JVM解释和编译指南( 三 )

可以看到在第五次迭代之后,代码片段被 JIT 编译了:
--------------------------------5 iteration21456bDemo::可以看到,与square方法一起,构造方法也被 JIT 编译了 。在for循环中调用square之前要先构造Demo实例,所以构造方法的解释次数同样达到 JIT 编译阈值 。这个例子说明了在解释发生之后何时 JIT 会介入 。
要查看编译后的代码,需要使用-XX:+PrintAssembly标志,该标志仅在库路径中有反汇编器时才起作用 。对于 OpenJDK,使用hsdis作为反汇编器 。下载合适版本的反汇编程序库,在本例中是hsdis-amd64.so,并将其放在Java_HOME/lib/server目录下 。使用时还需要在-XX:+PrintAssembly之前增加-XX:+UnlockDiagnosticVMOptions选项 。否则 , JVM 会给你一个警告 。
完整命令如下:
$ java -Xbatch -XX:+PrintCompilation -XX:CompileCommandFile=hotspot_compiler \\ -XX:-TieredCompilation -XX:CompileThreshold=5 -XX:+UnlockDiagnosticVMOptions \\ -XX:+PrintAssembly Demo[...]5 iteration17856bDemo::我只截取了输出中与Demo.java相关的部分 。
现在再来看看 AOT 编译 。它是在 JDK9 中引入的特性 。AOT 是用于生成.so这样的库文件的静态编译器 。用 AOT 可以将指定的类编译成.so库 。这个库可以直接执行,而不用解释或 JIT 编译 。如果 JVM 没有检测到 AOT 编译的代码,它会进行常规的解释和 JIT 编译 。
使用 AOT 编译的命令如下:
$ jaotc --output=libDemo.so Demo.class用下面的命令来查看共享库的符号表:
$ nm libDemo.so要使用生成的.so库,使用-XX:+
UnlockExperimentalVMOptions
-XX:AOTLibrary
$ java -XX:+UnlockExperimentalVMOptions -XX:AOTLibrary=./libDemo.so Demo1 iterationSquare(i) = 1Time taken= 7831139--------------------------------2 iterationSquare(i) = 4Time taken= 36619[...]10 iterationSquare(i) = 100Time taken= 42085JVM解释和编译指南 。小编来告诉你更多相关信息 。
JVM解释和编译指南从输出上看,跟完全用解释的情况没有区别 。为了确认 AOT 发挥了作用 , 使用-XX:+PrintAOT
$ java -XX:+UnlockExperimentalVMOptions -XX:AOTLibrary=./libDemo.so -XX:+PrintAOT Demo281loaded./libDemo.soaot library801aot[ 1]Demo.main([Ljava/lang/String;)V802aot[ 1]Demo.square(I)I803aot[ 1]Demo.要确认没有发生 JIT 编译 , 用如下命令:
$ java -XX:+UnlockExperimentalVMOptions -Xbatch -XX:+PrintCompilation \\ -XX:CompileCommandFile=hotspot_compiler -XX:-TieredCompilation \\ -XX:CompileThreshold=3 -XX:AOTLibrary=./libDemo.so -XX:+PrintAOT Demo191loaded./libDemo.soaot library771aot[ 1]Demo.square(I)I772aot[ 1]Demo.main([Ljava/lang/String;)V773aot[ 1]Demo.需要特别注意的是,修改被 AOT 编译了的源代码后,一定要重新生成.so库文件 。否则 , 过时的的 AOT 编译库文件不会起作用 。例如,修改square方法 , 使其计算立方值:
//Demo.javapublic class Demo {public int square(int i) throws Exception {return(i*i*i);}public static void main(String[] args) throws Exception {for (int i = 1; i 重新编译Demo.java
$ java Demo.java但不重新生成libDemo.so 。使用下面命令运行Demo
$ java -XX:+UnlockExperimentalVMOptions -Xbatch -XX:+PrintCompilation -XX:CompileCommandFile=hotspot_compiler -XX:-TieredCompilation -XX:CompileThreshold=3 -XX:AOTLibrary=./libDemo.so -XX:+PrintAOT Demo201loaded./libDemo.soaot library741njava.lang.invoke.MethodHandle::linkToStatic(LLLLLL)L (native)(static)2 iterationsqrt(i) = 8Time taken= 43838--------------------------------3 iteration13756bDemo::可以看到,虽然旧版本的libDemo.so被加载了,但 JVM 检测出它已经过时了 。每次生成.class文件时,都会在类文件中添加一个指纹,并在 AOT 库中保存该指纹 。修改源代码后类指纹与旧的 AOT 库中的指纹不匹配了,所以没有执行 AOT 编译生成的原生机器码 。从输出可以看出,现在实际上是 JIT 在起作用(注意-XX:CompileThreshold被设置为了 3) 。
AOT 和 JIT 之间的权衡如果你的目标是减少 JVM 的预热时间 , 请使用 AOT,这可以减少运行时负担 。问题是 AOT 没有足够的数据来决定哪段代码需要预编译为原生代码 。相比之下,JIT 在运行时起作用,却对预热时间有一定的影响 。然而,它将有足够的分析数据来更高效地编译和反编译代码 。