1.如何学习JIT?
查阅java.exe文档,有三个参数:-Xcomp
、-Xint
、-Xmixed
,引发了笔者的思考:
虽然JIT是JVM中技术含量极高的核心特性之一,但实战中我们却极少触及(除非您从事的是编译器相关的工作)。
我们应该如何快速了解JIT的各种编译模式?如何针对性地在实战灵活使用它们?本文将围绕上述问题与读者展开探讨。
2.对立统一:编译能力的技术指标
2.1.在折腾啥?
了解编译能力技术指标之前,我们先搞清楚现代编译器在折腾啥?
主流编程语言可以分为2种编译流派:
- 直奔主题派:编译期直接生成机器码,以C/C++为例:
- 含蓄婉约派:编译期不直接生成机器码,以Java为例:
这里存在一个有趣的技术细节:是不是含蓄婉约派都是在运行时获得机器码呢?
,答案是否定的:
- 纯解释器时代:在Java的初期版本,仅依靠解释器,在这个时期,Java是运行时获得机器码的。
- 解释器+即时编译器时代:纯解释器引发了
Java的执行性能远低于C/C++
的诟病,于是Java很快就有了即时编译器。在这个时期,Java虽然还是在运行时获得机器码,但机器码除了来自于解释器,很大部分可能来自于即时编译器(被JIT识别为热点代码的字节码会被编译成机器码缓存下来,减少解释器在运行时重复编译的损耗)。
- 提前编译器时代:随着云原生、大数据等业务领域出现,Java也提供了提前编译器。在这个时期,Java就有可能从编译期获得机器码。
上面这个技术细节的有趣之处在于:它体现了现代编译器在编译技术上面临的挑战与发展趋势。
《A Java vs. C++ performance evaluation: a 3D modeling benchmark》这篇论文,对Java和C++进行性能测试,得到的3个观点也在说明这个趋势:
- 在纯解释器模式下,Java速度比C++慢10+倍。
- 在解释器+即时编译模式下,Java比C++平均慢1.45到2.91倍。
- 在Long-running应用中(JVM有机会进入热机状态),Java比C++慢1.09~1.91倍。
- 在Short-running应用中(JVM无机会进入冷机状态),Java比C++慢2.72~5.61倍。
- JIT(即时编译器)无论如何优化,也无法忽视AOT(提前编译器)带来的优势。
我们可以看到,现代编译器在折腾3个东西:
- 静态编译(前端编译)
- 即时编译
- 提前编译
2.2.折腾的原动力:对立统一的编译能力技术指标
如果说互联网高速发展的源动力是人们对苍老师和PxxxHub的需要,那么现代编译器的驱动力是矛盾的评价指标:
- 代码优化质量
- 编译速度
首先,代码优化是很有灰度的工作:
无论编译器如何发展,它们都必须心存敬畏地讨好它们的神——CPU
代码优化就是在做讨好CPU
的工作。
但,过犹不及地讨好
就上升为了欺骗
,马屁拍到马腿上的后果就是过度地代码优化
。
其次,在高质量地优化代码的前提下,还要保证编译速度。
一个质量指标,一个效率指标,两个矛盾的指标,最终催生了对立统一的优化手段:
解释器+即时编译器混合模式:先基于解释器解释获得机器码,然后将热点代码交给即时编译器获得机器码并缓存
- 即时编译器优化先做激进优化,激进优化成功了就缓存激进优化后的机器码
- 即时编译器激进优化失败了,就降低代码优化级别,再缓存保守优化的机器码
提前编译模式:啥也不说了,直接一步到位的获得机器码
说明:在虚拟机类型的世界里,有个假设,静态编译永远不如即时编译和提前编译的代码优化质量好。
2.3.各领风骚
了解了编译器的技术指标,也就了解了编译器追求的方向,也就形成了这3类编译器各领风骚的技术发展现状。
- 各厂家持续在即时编译器领域发力
如下图所示,Hotspot虚拟机的即时编译器就包含了C1和C2,同时在Java10加入了C2的替代者Graal。
说明:C1的职责只是常规的编译优化,C2则承担了更多激进优化的任务。。
- 各厂家在探索提前编译器
如下图所示,Hotspot虚拟机提供了实验性质的jaotc。
3.实战中的应用场景
3.1.场景1:解释器+C1+C2
在实战环境中,解释模式+C1+C2是最常用的应用场景。
以Hotspot为例,解释器与即时编译器的配合过程:首先还是解释器工作,然后热点代码触发即时编译器,即时编译一旦出现激进优化失败就优化降级,甚至把执行权还给解释器。
上图特指Java7及更高版本,在这个前提下,才会有C1和C2,才会发生激进优化、优化降级,这个过程称为分层编译
。
说明:本文不展开分层编译原理。
3.2.场景2:解释器
如上图所示,解释器+即时编译器模式挺好,为啥还需要纯粹的解释器模式呢?实战中,这种一般用于性能调优和测试。
由于C1和C2的代码优化,我们在产品代码中编写的性能不太友好
的代码可能被JVM自动优化掉。
但,我们就不能精准、高效地发现这些性能不友好
的代码了。
所以JVM才通过java.exe的参数提供了这种强制关闭即时编译器的开关。
JVM默认采用混合模式运行(无论JVM处于client模式还是server模式):
在java.exe的参数中,提供了三个参数,强制控制运行模式:
- -Xint:强制虚拟机运行于解释模式。
- -Xcomp:强制虚拟机运行于编译模式
- -Xmixed:运行于混合模式
4.总结
本文主要内容:
- 编译器技术指标:代码优化质量、编译速度。
- 各厂商重点在即时编译器领域发力,在提前编译器领域探索。
- 生产环境上,通常采用解释器+C1+C2模式。
- 开发态做性能调优,可以采用强制解释器模式。
5.参考文献
https://docs.oracle.com/javase/8/docs/technotes/tools/windows/