你的Java代码对JIT编译友好么?(2)

PrintCompilation的输出结果会提供运行时正在编译的方法的信息,Jarscan工具的输出结果可以告诉我们哪些方法不能进行JIT编译。结合两者,我们就可以清楚地知道哪些方法进行了编译,哪些没有进行。另外,PrintCompilation选项可以在线上环境使用,因为开启这个选项几乎不会影响JIT编译器的性能。

但是,PrintCompilation也存在着两个小问题,有时候会显得不是那么方便:

输出的结果中未包含方法的签名,如果存在重载方法,区分起来则比较困难。

Hotspot虚拟机目前不能将结果输出到单独的文件中,目前只能是以标准输出的形式展示。

上述的第二个问题的影响在于PrintCompilation的日志会和其他常用的日志混在一起。对于大多数服务器端程序来说,我们需要一个过滤进程来将PrintCompilation的日志过滤到一个独立的日志中。最简单的判断一个方法否是JIT友好的途径就是遵循下面这个简单的步骤:

确定程序中位于要处理的关键路径上的方法。

检查这些方法没有出现在Jarscan的输出结果中。

检查这些方法确实出现在了PrintCompilation的输出结果中。

如果一个方法超过了内联的临界值,大多数情况下最常用的方法就是讲这个重要的方法拆分成多个可以进行内联的小方法,这样修改之后通常会获取更好的执行效率。但是对于所有的性能优化而言,优化之前的执行效率需要测量记录,并且需要需要同优化后的数据进行对比之后,才能决定是否进行优化。为了性能优化而做出的改变不应该是盲目的。

几乎所有的Java程序都依赖大量的提供关键功能的库。Jarscan可以帮助我们检测哪些库或者框架的方法超过了内联的临界值。举一个具体的例子,我们这里检查JVM主要的运行时库 rt.jar文件。

为了让结果有点意思,我们分别比较Java 7 和Java 8,并查看这个库的变化。在开始之前我们需要安装Java 7 和 Java8 JDK。首先,我们分别运行Jarscan扫描各自的rt.jar文件,并得到用来后续分析的报告结果:

$ ./jarScan.sh /Library/Java/JavaVirtualMachines/jdk1.7.0_71.jdk/Contents/Home/jre/lib/rt.jar > large_jre_methods_7u71.txt $ ./jarScan.sh /Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/jre/lib/rt.jar > large_jre_methods_8u25.txt

上述操作结束之后,我们得到两个CSV文件,一个是JDK 7u71的结果,另一个是JDK 8u25。然后我们看一看不同的版本内联情况有哪些变化。首先,一个最简单的判断验证方式,看一看不同版本的JRE中有多少对JIT不友好的方法。

$ wc -l large_jre_methods_* 3684 large_jre_methods_7u71.txt 3576 large_jre_methods_8u25.txt

我们可以看到,相比Java 7,Java 8 少了100多个内联不友好的方法。下面继续深入研究,看看一些关键的包的变化。为了便于理解如何操作,我们再次介绍一下Jarscan的输出结果。Jarscan的输出结果有如下3个属性组成:

"<package>","<method name and signature>",<num of bytes>

了解了上述的格式,我们可以利用一些Unix文本处理的工具来研究报告结果。比如,我们想看一下Java 7 和 Java 8 这两个版本中java.lang包下哪些方法变得内联友好了:

$ cat large_jre_methods_7u71.txt large_jre_methods_8u25.txt | grep -i ^\"java.lang | sort | uniq -c

上面的语句使用grep命令过滤出每份报告中以java.lang开头的行,即只显示位于包java.lang中的类的内联不友好的方法。sort | uniq -c 是一个比较老的Unix小技巧,首先将讲行信息进行排序(相同的信息将聚集到一起),然后对上面的排序数据进行去重操作。另外本命令还会统计一个当前行信息重复的次数,这个数据位于每一行信息的最开始部分。让我们看一下上述命令的执行结果:

$ cat large_jre_methods_7u71.txt large_jre_methods_8u25.txt | grep -i ^\"java.lang | sort | uniq -c 2 "java.lang.CharacterData00","int getNumericValue(int)",835 2 "java.lang.CharacterData00","int toLowerCase(int)",1339 2 "java.lang.CharacterData00","int toUpperCase(int)",1307 // ... skipped output 2 "java.lang.invoke.DirectMethodHandle","private static java.lang.invoke.LambdaForm makePreparedLambdaForm(java.lang.invoke.MethodType,int)",613 1 "java.lang.invoke.InnerClassLambdaMetafactory","private java.lang.Class spinInnerClass()",497 // ... more output ----

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/50f5f9baed18ac1d3dbc0127d5cf91e6.html