1.如何学习JIT?

1

查阅java.exe文档,有三个参数:-Xcomp-Xint-Xmixed,引发了笔者的思考:

虽然JIT是JVM中技术含量极高的核心特性之一,但实战中我们却极少触及(除非您从事的是编译器相关的工作)。

我们应该如何快速了解JIT的各种编译模式?如何针对性地在实战灵活使用它们?本文将围绕上述问题与读者展开探讨。

2.对立统一:编译能力的技术指标

2.1.在折腾啥?

了解编译能力技术指标之前,我们先搞清楚现代编译器在折腾啥?

主流编程语言可以分为2种编译流派:

  • 直奔主题派:编译期直接生成机器码,以C/C++为例:

2

  • 含蓄婉约派:编译期不直接生成机器码,以Java为例:

3

这里存在一个有趣的技术细节:是不是含蓄婉约派都是在运行时获得机器码呢?,答案是否定的:

  • 纯解释器时代:在Java的初期版本,仅依靠解释器,在这个时期,Java是运行时获得机器码的。
  • 解释器+即时编译器时代:纯解释器引发了Java的执行性能远低于C/C++的诟病,于是Java很快就有了即时编译器。在这个时期,Java虽然还是在运行时获得机器码,但机器码除了来自于解释器,很大部分可能来自于即时编译器(被JIT识别为热点代码的字节码会被编译成机器码缓存下来,减少解释器在运行时重复编译的损耗)。

image-20210806114107579

  • 提前编译器时代:随着云原生、大数据等业务领域出现,Java也提供了提前编译器。在这个时期,Java就有可能从编译期获得机器码。

image-20210806114918865

上面这个技术细节的有趣之处在于:它体现了现代编译器在编译技术上面临的挑战与发展趋势。

《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的需要,那么现代编译器的驱动力是矛盾的评价指标:

  • 代码优化质量
  • 编译速度

image-20210806152946493

首先,代码优化是很有灰度的工作:

无论编译器如何发展,它们都必须心存敬畏地讨好它们的神——CPU

代码优化就是在做讨好CPU的工作。

但,过犹不及地讨好就上升为了欺骗,马屁拍到马腿上的后果就是过度地代码优化

image-20210806135534437

其次,在高质量地优化代码的前提下,还要保证编译速度。

一个质量指标,一个效率指标,两个矛盾的指标,最终催生了对立统一的优化手段

  • 解释器+即时编译器混合模式:先基于解释器解释获得机器码,然后将热点代码交给即时编译器获得机器码并缓存

    • 即时编译器优化先做激进优化,激进优化成功了就缓存激进优化后的机器码
    • 即时编译器激进优化失败了,就降低代码优化级别,再缓存保守优化的机器码
  • 提前编译模式:啥也不说了,直接一步到位的获得机器码

说明:在虚拟机类型的世界里,有个假设,静态编译永远不如即时编译和提前编译的代码优化质量好。

2.3.各领风骚

了解了编译器的技术指标,也就了解了编译器追求的方向,也就形成了这3类编译器各领风骚的技术发展现状。

  • 各厂家持续在即时编译器领域发力

如下图所示,Hotspot虚拟机的即时编译器就包含了C1和C2,同时在Java10加入了C2的替代者Graal。

说明:C1的职责只是常规的编译优化,C2则承担了更多激进优化的任务。。

  • 各厂家在探索提前编译器

如下图所示,Hotspot虚拟机提供了实验性质的jaotc。

5

3.实战中的应用场景

3.1.场景1:解释器+C1+C2

在实战环境中,解释模式+C1+C2是最常用的应用场景。

以Hotspot为例,解释器与即时编译器的配合过程:首先还是解释器工作,然后热点代码触发即时编译器,即时编译一旦出现激进优化失败就优化降级,甚至把执行权还给解释器。

6

上图特指Java7及更高版本,在这个前提下,才会有C1和C2,才会发生激进优化、优化降级,这个过程称为分层编译

说明:本文不展开分层编译原理。

3.2.场景2:解释器

image-20210806144744523

如上图所示,解释器+即时编译器模式挺好,为啥还需要纯粹的解释器模式呢?实战中,这种一般用于性能调优和测试。

由于C1和C2的代码优化,我们在产品代码中编写的性能不太友好的代码可能被JVM自动优化掉。

但,我们就不能精准、高效地发现这些性能不友好的代码了。

所以JVM才通过java.exe的参数提供了这种强制关闭即时编译器的开关。

1

JVM默认采用混合模式运行(无论JVM处于client模式还是server模式):

image-20210806145459698

在java.exe的参数中,提供了三个参数,强制控制运行模式:

  • -Xint:强制虚拟机运行于解释模式。
  • -Xcomp:强制虚拟机运行于编译模式
  • -Xmixed:运行于混合模式

4.总结

本文主要内容:

  • 编译器技术指标:代码优化质量、编译速度。
  • 各厂商重点在即时编译器领域发力,在提前编译器领域探索。
  • 生产环境上,通常采用解释器+C1+C2模式。
  • 开发态做性能调优,可以采用强制解释器模式。

5.参考文献

https://docs.oracle.com/javase/8/docs/technotes/tools/windows/