我在这里看到了很多线程,它们比较并试图回答哪个更快:newInstance或new operator。
从源代码来看,newInstance应该是的慢得多的,我的意思是它做了太多的安全检查并使用反射。我决定测量,第一次运行jdk-8。下面是使用jmh的代码。
@BenchmarkMode(value = { Mode.AverageTime, Mode.SingleShotTime })
@Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
@State(Scope.Benchmark)
public class TestNewObject {
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder().include(TestNewObject.class.getSimpleName()).build();
new Runner(opt).run();
}
@Fork(1)
@Benchmark
public Something newOperator() {
return new Something();
}
@SuppressWarnings("deprecation")
@Fork(1)
@Benchmark
public Something newInstance() throws InstantiationException, IllegalAccessException {
return Something.class.newInstance();
}
static class Something {
}
}我不认为这里有什么大的惊喜(JIT做了很多优化,使这种差异没有那么大):
Benchmark Mode Cnt Score Error Units
TestNewObject.newInstance avgt 5 7.762 ± 0.745 ns/op
TestNewObject.newOperator avgt 5 4.714 ± 1.480 ns/op
TestNewObject.newInstance ss 5 10666.200 ± 4261.855 ns/op
TestNewObject.newOperator ss 5 1522.800 ± 2558.524 ns/op热代码的区别可能在2x附近,而单次拍摄时间则差得多。
现在,我切换到jdk-9 (在重要情况下构建157 )并运行相同的代码。其结果是:
Benchmark Mode Cnt Score Error Units
TestNewObject.newInstance avgt 5 314.307 ± 55.054 ns/op
TestNewObject.newOperator avgt 5 4.602 ± 1.084 ns/op
TestNewObject.newInstance ss 5 10798.400 ± 5090.458 ns/op
TestNewObject.newOperator ss 5 3269.800 ± 4545.827 ns/op在热代码中,这是一个叫50x差的。我正在使用最新的jmh版本(1.19.SNAPSHOT)。
在测试中再添加一个方法之后:
@Fork(1)
@Benchmark
public Something newInstanceJDK9() throws Exception {
return Something.class.getDeclaredConstructor().newInstance();
}以下是JDK-9的总体结果:
TestNewObject.newInstance avgt 5 308.342 ± 107.563 ns/op
TestNewObject.newInstanceJDK9 avgt 5 50.659 ± 7.964 ns/op
TestNewObject.newOperator avgt 5 4.554 ± 0.616 ns/op 有人能解释一下为什么会有这么大的差别吗?
发布于 2017-03-15 00:50:48
首先,这个问题与模块系统无关(直接).。
我注意到,即使使用JDK 9,newInstance的第一次热身迭代也与JDK 8一样快。
# Fork: 1 of 1
# Warmup Iteration 1: 10,578 ns/op <-- Fast!
# Warmup Iteration 2: 246,426 ns/op
# Warmup Iteration 3: 242,347 ns/op这意味着JIT编译中出现了一些问题。
-XX:+PrintCompilation确认基准测试是在第一次迭代之后重新编译的:
10,762 ns/op
# Warmup Iteration 2: 1541 689 ! 3 java.lang.Class::newInstance (160 bytes) made not entrant
1548 692 % 4 bench.generated.NewInstance_newInstance_jmhTest::newInstance_avgt_jmhStub @ 13 (56 bytes)
1552 693 4 bench.generated.NewInstance_newInstance_jmhTest::newInstance_avgt_jmhStub (56 bytes)
1555 662 3 bench.generated.NewInstance_newInstance_jmhTest::newInstance_avgt_jmhStub (56 bytes) made not entrant
248,023 ns/op然后,-XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining指出了内联问题:
1577 667 % 4 bench.generated.NewInstance_newInstance_jmhTest::newInstance_avgt_jmhStub @ 13 (56 bytes)
@ 17 bench.NewInstance::newInstance (6 bytes) inline (hot)
! @ 2 java.lang.Class::newInstance (160 bytes) already compiled into a big method“已经编译成一个大方法”消息意味着编译器无法内联Class.newInstance调用,因为被调用方的编译大小大于InlineSmallCode值(默认情况下为2000 )。
当我用-XX:InlineSmallCode=2500重新运行基准时,它又变得很快了。
Benchmark Mode Cnt Score Error Units
NewInstance.newInstance avgt 5 8,847 ± 0,080 ns/op
NewInstance.operatorNew avgt 5 5,042 ± 0,177 ns/op您知道,JDK 9现在将G1作为默认的GC。如果回到并行GC,即使使用默认的InlineSmallCode,基准测试也将是快速的。
用-XX:+UseParallelGC重新运行JDK 9基准测试
Benchmark Mode Cnt Score Error Units
NewInstance.newInstance avgt 5 8,728 ± 0,143 ns/op
NewInstance.operatorNew avgt 5 4,822 ± 0,096 ns/op每当发生对象存储时,G1都需要设置一些屏障,这就是为什么编译后的代码变得更大的原因,因此Class.newInstance超过了默认的InlineSmallCode限制。编译后的Class.newInstance变得更大的另一个原因是反射代码在JDK 9中被稍微重写了。
TL; JIT博士未能内联
Class.newInstance,因为InlineSmallCode限制已经超过了。由于JDK 9中反射代码的更改以及默认GC已更改为G1,编译后的G1版本变得更大。
发布于 2017-03-14 16:42:00
除以下部分外,Class.newInstance()的实现基本相同:
Java 8:
Constructor<T> tmpConstructor = cachedConstructor;
// Security check (same as in java.lang.reflect.Constructor)
int modifiers = tmpConstructor.getModifiers();
if (!Reflection.quickCheckMemberAccess(this, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
if (newInstanceCallerCache != caller) {
Reflection.ensureMemberAccess(caller, this, null, modifiers);
newInstanceCallerCache = caller;
}
}Java 9
Constructor<T> tmpConstructor = cachedConstructor;
// Security check (same as in java.lang.reflect.Constructor)
Class<?> caller = Reflection.getCallerClass();
if (newInstanceCallerCache != caller) {
int modifiers = tmpConstructor.getModifiers();
Reflection.ensureMemberAccess(caller, this, null, modifiers);
newInstanceCallerCache = caller;
}正如您所看到的,Java 8有一个quickCheckMemberAccess,它允许绕过昂贵的操作,比如Reflection.getCallerClass()。我想,这个快速检查已经被删除了,因为它与新的模块访问规则不兼容。
但更重要的是。JVM可以使用可预测类型优化反射实例化,而Something.class.newInstance()引用完全可预测的类型。这种优化可能会变得不那么有效。有几个可能的原因:
Class.newInstance()被否决以来,一些支持被故意移除(在我看来不太可能)https://stackoverflow.com/questions/42786629
复制相似问题