Java 8编译代码与Java 11编译代码

3

我们目前使用Java 8编译的代码,但在Java 11虚拟机上运行。现在我们正在尝试将代码移植到Java 11编译环境中。想知道Java 8编译的代码与Java 11编译的代码相比,在性能上是否有任何优势,因为两个编译器将生成不同的类文件(字节码)?在效率方面,两者有何不同?


2
你可以使用 javap 反编译它们并进行比较。我敢打赌,它们之间没有太大的区别,如果有的话。性能来自 JIT 编译器,而不是字节码。 - Pavel Smirnov
1
@PAvelSmirnov,不完全正确,有一个相当大的区别,它是在Java 9中添加的:https://openjdk.java.net/jeps/280 更新:此外,还有这个:https://openjdk.java.net/jeps/181 - M. Prokhorov
1个回答

11

javac不是优化编译器,因此通常情况下,不要期望它会从发布到发布生成更快的字节码。优化是JVM的工作。

与此同时,Java编译器确实支持新的语言特性并且可能支持新的JVM特性。其中一些确实会对性能产生影响。JDK 9到JDK 11中最显著的例子如下。

  1. JEP 280: Indify String Concatenation (JDK 9).

    This JEP changes the way how string concatenation expressions are compiled. Before JDK 9, string + expression was translated to

    new StringBuilder().append()...append().toString();
    

    Although JIT recognizes such chains and tries to optimize them in runtime, this optimization is fragile and does not always work as expected. Compiling string concatenation with invokedynamic gives the JVM more freedom to produce better code. You may find the detailed explanation and benchmarks in the notes to this JEP.

  2. JEP 181: Nest-Based Access Control (JDK 11)

    This JEP solves the problem of accessing private members of nested classes. Before JDK 11, Java Compiler generated synthetic bridge methods for them (example).

    At first glance, this has nothing to do with performance. However, in marginal cases an additional synthetic method may break inlining due to inlining depth limit.

    Nest-Based Access Control allows nestmate classes to access private members of each other without synthetic bridges, thus reducing risk of accidential performance degradation.

更新

之前我在列表中包含了JDK-8175883:增强for循环的字节码生成,但是正如@Holger在评论中指出的那样,这种“优化”实际上并没有起作用。

结论

Java编译器的更改主要涉及新的语言/JVM功能。字节码级别的优化不是目标。然而,其中一些更改可能(间接地)影响性能。无论如何,重新编译代码可能带来的性能收益通常非常小,以至于在实际应用程序中您甚至都不会注意到它们。


2
JDK-8175883 似乎是一个虚假修复。我尝试了 for(String s: List.of("foo", "bar")) {System.out.println(s);} foo();,在 JDK 9 和 JDK 10(以及任何 JDK 版本直到 16)之间的字节码没有任何区别。在 foo() 调用期间,Iterator 引用仍然是悬空的,就像以前一样。对我来说并不重要,因为我认为插入显式的 null 声明语句并不能改进什么。这将使每个 for-each 循环的字节码变得更大,以服务于一个罕见的角落情况,否则永远不会被处理,例如 try-with-resource、finally 等可能仍然会引入悬空的合成变量。 - Holger
1
作为补充,正如此答案中所讨论的,JDK 11中try-with-resource语句的编译形式有了显著的改进。 - Holger
1
@Holger 很好的发现。事实证明,for循环的“优化”在JDK-8194836中被(暂时?)撤回,并且从未返回。在JDK 10发布说明中提到这种优化让我感到困惑。感谢您的再次确认。 - apangin
关于try-with-resource的更改,是否有证据表明它会影响性能? - apangin
1
我认为它不会影响运行时性能,因为大多数构件都是死代码。只有一些条件语句在解释执行时才有意义。但验证器肯定会少做一些工作。而且需要加载的代码也更少了。我猜,你需要使用很多try-with-resource语句(嵌套,以达到最大化),才能注意到差异。 - Holger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接