JVM 解释和编译指南( 二 )

在这个程序的 main() 方法中创建了一个 Demo 对象的实例,并调用该实例的 square()方法,然后显示 for 循环迭代变量的平方值 。编译并运行它:
$ javac Demo.java$ java Demo1 iterationSquare(i) = 1Time taken= 8432439--------------------------------2 iterationSquare(i) = 4Time taken= 54631--------------------------------...--------------------------------10 iterationSquare(i) = 100Time taken= 66498--------------------------------上面的结果是由谁产生的呢?是解释器 , JIT 还是 AOT?在目前的情况下,它完全是通过解释产生的 。我是怎么得出这个结论的呢?只有代码被解释的次数必须超过某个阈值时,这些热点代码片段才会被加入 JIT 编译队列 。只有这时,JIT 编译才会发挥作用 。使用以下命令查看 JDK 11 中的该阈值:
$ java -XX:+PrintFlagsFinal -version | grep CompileThreshold intx CompileThreshold= 10000{pd product} {default}[...]openjdk version "11.0.13" 2021-10-19OpenJDK Runtime Environment 18.9 (build 11.0.13+8)OpenJDK 64-Bit Server VM 18.9 (build 11.0.13+8, mixed mode, sharing)上面的输出表明,一段代码被解释 10,000 次才符合 JIT 编译的条件 。这个阈值是否可以手动调整呢?是否有 JVM 标志可以指示出方法是否被 JIT 编译了呢?答案是肯定的,而且有多种方式可以达到这个目的 。
使用 -XX:+PrintCompilation 选项可以查看一个方法是否被 JIT 编译 。除此之外,使用 -Xbatch 标志可以提高输出的可读性 。如果解释和 JIT 同时发生,-Xbatch 可以帮助区分两者的输出 。使用这些标志如下:
$ java -Xbatch-XX:+PrintCompilationDemo341b3java.util.concurrent.ConcurrentHashMap::tabAt (22 bytes)352n 0jdk.internal.misc.Unsafe::getObjectVolatile (native)353b3java.lang.Object::<init> (1 bytes)[...]210269n 0java.lang.reflect.Array::newArray (native)(static)211270b3java.lang.String::substring (58 bytes)[...]--------------------------------10 iterationSquare(i) = 100Time taken= 50150--------------------------------注意 , 上面命令的实际输出太长了,这里我只是截取了一部分 。输出很长的原因是除了 Demo 程序的代码外,JDK 内部类的函数也被编译了 。由于我的重点是 Demo.java 代码,我希望排除内部包的函数来简化输出 。通过选项 -XX:CompileCommandFile 可以禁用内部类的 JIT:
$ java -Xbatch -XX:+PrintCompilation -XX:CompileCommandFile=hotspot_compiler Demo在选项 -XX:CompileCommandFile 指定的文件 hotspot_compiler 中包含了要排除的包:
$ cat hotspot_compilerquietexclude java/* *exclude jdk/* *exclude sun/* *第一行的 quiet 告诉 JVM 不要输出任何关于被排除类的内容 。用 -XX:CompileThreshold 将 JIT 阈值设置为 5 。这意味着在解释 5 次之后,就会进行 JIT 编译:
$ java -Xbatch -XX:+PrintCompilation -XX:CompileCommandFile=hotspot_compiler -XX:CompileThreshold=5 Demo471n 0java.lang.invoke.MethodHandle::linkToStatic(LLLLLL)L (native)(static)472n 0java.lang.invoke.MethodHandle::invokeBasic(LLLLL)L (native)473n 0java.lang.invoke.MethodHandle::linkToSpecial(LLLLLLL)L (native)(static)484n 0java.lang.invoke.MethodHandle::linkToStatic(L)I (native)(static)485n 0java.lang.invoke.MethodHandle::invokeBasic()I (native)486n 0java.lang.invoke.MethodHandle::linkToSpecial(LL)I (native)(static)[...]1 iteration6940n 0java.lang.invoke.MethodHandle::linkToStatic(ILIIL)I (native)(static)[...]Square(i) = 17848n 0java.lang.invoke.MethodHandle::linkToStatic(ILIJL)I (native)(static)7949n 0java.lang.invoke.MethodHandle::invokeBasic(ILIJ)I (native)[...]8654n 0java.lang.invoke.MethodHandle::invokeBasic(J)L (native)8755n 0java.lang.invoke.MethodHandle::linkToSpecial(LJL)L (native)(static)Time taken= 8962738--------------------------------2 iterationSquare(i) = 4Time taken= 26759--------------------------------10 iterationSquare(i) = 100Time taken= 26492--------------------------------好像输出结果跟只用解释时并没有什么区别 。根据 Oracle 的文档 , 这是因为只有禁用 TieredCompilation 时 -XX:CompileThreshold 才会生效:
$ java -Xbatch -XX:+PrintCompilation -XX:CompileCommandFile=hotspot_compiler -XX:-TieredCompilation -XX:CompileThreshold=5 Demo1241njava.lang.invoke.MethodHandle::linkToStatic(LLLLLL)L (native)(static)1272njava.lang.invoke.MethodHandle::invokeBasic(LLLLL)L (native)[...]1 iteration18740njava.lang.invoke.MethodHandle::linkToStatic(ILIIL)I (native)(static)[...](native)(static)21254njava.lang.invoke.MethodHandle::invokeBasic(J)L (native)21255njava.lang.invoke.MethodHandle::linkToSpecial(LJL)L (native)(static)Time taken= 12337415[...]--------------------------------4 iterationSquare(i) = 16Time taken= 37183--------------------------------5 iteration21456bDemo::<init> (5 bytes)21557bDemo::square (16 bytes)Square(i) = 25Time taken= 983002--------------------------------6 iterationSquare(i) = 36Time taken= 81589[...]10 iterationSquare(i) = 100Time taken= 52393


推荐阅读