分层编译
Last updated
Last updated
分层编译是什么?
在JVM中存在三种代码编译执行模式,分别是解释执行、编译执行与分层编译执行。
HotSpot 内置两种JIT编译器,分别是Client启动时的c1编译器和Server启动时的c2编译器:
c1编译器:目标是使程序尽快进入可执行的阶段,所以在编译前需要搜集的信息比c2要少,编译速度因此提高很多,但是付出的代价是编译之后的代码执行效率比较低,但尽管如此,c1编译出来的代码在性能上比解释执行的性能已经有很大的提升;
c2编译器:c2在将代码编译成机器代码的时候需要搜集大量的统计信息以便在编译的时候进行优化,因此编译出来的代码执行效率比较高,代价是程序启动时间比较长,而且需要执行比较长的时间,才能达到最高性能;
分层编译,就是一种折中方式,在系统执行初期,执行频率比较高的代码先被c1编译器编译,以便尽快进入编译执行,然后随着时间的推移,执行频率较高的代码再被c2编译器编译,以达到最高的性能。
怎么理解分层编译中的“分层”?
Java7之前,开发人员需要根据业务场景选择合适的编译器,对于启动性能要求比较高,不长期运行的场景选择c1,对应-client模式。对于长期运行,峰值性能要求比较高的场景选择c2,对应-server模式。Java7开始引入了分层编译,结合c1和c2的优势,追求启动性能和峰值性能的一个平衡。
分层编译将JVM的执行状态分为5个层次:
第0层:解释执行。【解释器】
第1层:执行不带profiling的C1代码。(profiling:收集能够反映程序执行状态的数据。其中最基本的统计数据就是方法的调用次数,以及循环回边的执行次数。)【C1】
第2层:执行仅带方法调用次数以及循环回边执行次数profiling的C1代码。【C1】
第3层:执行带所有profiling的C1代码。【C1】
第4层:执行C2代码。【C2】
编译出的代码执行效率从大到小依次是:第4层 > 第1层 > 第2层 > 第3层
这5层运行路径的场景?
常规路径:代表编译的一般情况,热点方法从解释执行到被3层的C1编译,最后被4层的C2编译。
简单方法:如果方法比较小(比如Java服务中常见的getter/setter方法),3层的profiling没有收集到有价值的数据,JVM就会断定该方法对于C1代码和C2代码的执行效率相同,就会执行图中第2条路径。在这种情况下,JVM会在3层编译之后,放弃进入C2编译,直接选择用1层的C1编译运行。
c1忙碌:在C1忙碌的情况下,执行图中第3条路径,在解释执行过程中对程序进行profiling ,根据信息直接由第4层的C2编译。
c2忙碌:C1中的执行效率是1层>2层>3层,第3层一般要比第2层慢35%以上,所以在C2忙碌的情况下,执行图中第4条路径。这时方法会被2层的C1编译,然后再被3层的C1编译,以减少方法在3层的执行时间。
反优化:如果编译器做了一些比较激进的优化,比如分支预测,在实际运行时发现预测出错,这时就会进行反优化,重新进入解释执行,图中第⑤条执行路径代表的就是反优化。
总结:JVM会根据程序的运行情况动态选择合适的编译路径,来尽可能保证系统有更好的性能。
参考:
https://blog.csdn.net/Rylan11/article/details/81606119
https://www.jianshu.com/p/4214df5df334
https://blog.csdn.net/weixin_41947378/article/details/113919808
https://titanwolf.org/Network/Articles/Article?AID=7b7b5ba8-204b-4f76-87f1-33d94c0dfa0c#gsc.tab=0
https://blog.csdn.net/bear_lam/article/details/79400657
https://www.jianshu.com/p/b9a5cbef12f2
http://zhongmingmao.me/2019/01/02/jvm-advanced-jit/
https://tech.meituan.com/2020/10/22/java-jit-practice-in-meituan.html