Java jdk-9/jdk-8和jmh中的newInstance与new

Java jdk-9/jdk-8和jmh中的newInstance与new,java,performance,java-8,jmh,java-9,Java,Performance,Java 8,Jmh,Java 9,我在这里看到了许多比较并尝试回答哪个更快的线程:newInstance或newoperator 看看源代码,似乎newInstance应该慢得多,我的意思是它执行了很多安全检查并使用了反射。我决定进行测量,首先运行jdk-8。下面是使用jmh的代码 @BenchmarkMode(value = { Mode.AverageTime, Mode.SingleShotTime }) @Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECON

我在这里看到了许多比较并尝试回答哪个更快的线程:
newInstance
newoperator

看看源代码,似乎
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做了很多优化,使这种差异没有那么大):

热代码的差异约为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
这在热代码中是一个50倍的差别。我使用的是最新的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    

有人能解释一下为什么会有如此大的差异吗?

类的实现。newInstance()除了以下部分外,基本上是相同的:

Java 8:
Constructor tmpConstructor=cachedConstructor;
//安全检查(与java.lang.reflect.Constructor中的相同)
int modifiers=tmpConstructor.getModifiers();
if(!Reflection.quickCheckMemberAccess(此,修饰符)){
Class caller=Reflection.getCallerClass();
if(newInstanceCallerCache!=调用者){
EnsuremberAccess(调用者、this、null、修饰符);
newInstanceCallerCache=调用者;
}
}
爪哇9
Constructor tmpConstructor=cachedConstructor;
//安全检查(与java.lang.reflect.Constructor中的相同)
Class caller=Reflection.getCallerClass();
if(newInstanceCallerCache!=调用者){
int modifiers=tmpConstructor.getModifiers();
EnsuremberAccess(调用者、this、null、修饰符);
newInstanceCallerCache=调用者;
}
如您所见,Java8有一个
quickCheckMemberAccess
,它允许绕过昂贵的操作,如
Reflection.getCallerClass()
。我猜这个快速检查已经被删除了,因为它与新的模块访问规则不兼容

但还有更多。JVM可能使用可预测的类型优化反射实例化,并且
Something.class.newInstance()
引用完全可预测的类型。这种优化可能会变得不那么有效。有几个可能的原因:

  • 新的模块访问规则使过程复杂化
  • 由于
    Class.newInstance()
    已被弃用,一些支持已被故意删除(对我来说似乎不太可能)
  • 由于上面显示的已更改的实现代码,HotSpot无法识别触发优化的某些代码模式

首先,问题与模块系统(直接)无关。

我注意到,即使使用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
然后
-XX:+UnlockDiagnosticVMOptions-XX:+PrintInLine
指向内联问题:

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

“已编译成大方法”消息表示编译器未能内联
类。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
您知道,JDK9现在有G1作为默认GC。如果我回到并行GC,即使使用默认的
InlineSmallCode
,基准测试也会很快

使用
-XX:+UseParallelGC
重新运行JDK9基准测试:

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;DRJIT未能内联
类。newInstance
,因为已超过
InlineSmallCode
限制。由于JDK 9中反射代码的更改以及默认GC已更改为G1,编译版本的
Class.newInstance
已变得更大


您使用的是带有jigsaw的JDK9构建吗?这很重要,因为系统中会有许多额外的模块访问检查,JIT可能还不知道如何很好地处理。
Class.newInstance()
在Java 9中被弃用。推荐的替代方案,
clazz.getDeclaredConstructor().newInstance()
的性能会很有趣…@Holger good point补充道。差异仍然是10倍,更好,但远不是2倍……你能做另一个测试吗,现在在
Something
中使用一个非
public
(默认访问)构造函数?这对像Spring这样的反射密集型框架来说不是一个大问题吗?也许这值得一份报告,以便使方法变得更小(例如,通过将一些代码提取到单独的方法中)。@KirillRakhman这不应该是一个问题,因为在现实生活场景中,
newInstance
无论如何都不可能内联。我无法想象一个合理的情况,当同一个构造函数通过反射在同一个地方被多次调用时。在最初的问题中,性能的提高只是因为JIT适应了调用特定的方法。很好的解释,甚至有一个tldr!
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
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
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
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