概念
即时编译是用来提升应用运行效率的技术。代码会先在JVM上解释执行,之后反复执行的热点代码会被即时翻译成为机器码,直接运行在底层硬件上。
分层编译模式
-
HotSpot包含多个即时编译器:C1、C2和Graal(Java 10,实验性)
-
在Java 7之前,需要根据程序的特性选择对应的即时编译器
- 对于执行时间较短或对启动性能有要求的程序,采用编译效率较快的C1,对应参数:-client
- 对于执行时间较长或对峰值性能有要求的程序,采用生成代码执行效率较快的C2,对应参数:-server
-
Java 7引入了分层编译(-XX:+TieredCompilation),综合了C1的启动性能优势和C2的峰值性能优势
-
分层编译将JVM的执行状态分了5个层次
- 0:解释执行(也会profiling)
- 1:执行不带profiling的C1代码
- 2:执行仅带方法调用次数和循环回边执行次数profiling的C1代码
- 3:执行带所有profiling的C1代码
- 4:执行C2代码
C1代码指C1生成的机器码;C2代码指C2生成的机器码
对于C1代码三种执行状态 1层>2层>3层(profiling越多,执行的效率越低)
C2代码的执行效率高于C1 大约30%
- profiling:在程序执行过程中,收集能够反映程序执行状态的
数据
(所收集的数据被叫做profile)。
编译路径
- 对于分层编译的5个层次的执行状态中,1层和4层为终止状态。(当一个方法被终止状态编译后,如果编译后的代码没有失效,那么JVM不会再次发出该方法的编译请求)
- common:通常情况下,热点方法会被3层的C1编译,然后再被4层的C2编译
- trivaial method: 如果方法的字节码数目较少(如getter/setter),并且3层的profiling没有可收集的数据,JVM会在3层的C1编译后,直接选用1层的C1编译
- c1 busy: 在C1忙碌的情况下,JVM在解释执行过程中对程序进行profiling,而后直接由4层的C2编译
- c2 busy: 在C2忙碌的情况下,方法会被2层的C1编译,然后再被3层的C1编译,以减少方法在3层的执行时间
JIT的触发
-
JVM是依据方法的调用次数以及循环回边的执行次数来触发JIT的
-
在不启动分层编译时
当方法的调用次数和循环回边的次数的和超过-XX:CompileThreshold,便会触发JIT(使用C1时,该值为1500,使用C2时,该值为10000) -
在启动分层编译时
阈值大小是动态调整的。 -
决定一个方式是否是热点代码的因素,及时编译器是根据以下这两个计数器的和来触发的
- 方法的调用次数
- 循环回边的执行次数
优化
当方法被 3 层 C1 所编译时,生成的 C1 代码将收集条件跳转指令的分支 profile,以及类型相关指令的类型 profile。在部分极端情况下,Java 虚拟机也会在解释执行过程中收集这些 profile。
基于分支 profile 的优化
以及基于类型 profile 的优化
都将对程序今后的执行作出假设。这些假设将精简所要编译的代码的控制流以及数据流。在假设失败的情况下,Java 虚拟机将采取去优化,退回至解释执行并重新收集相关的 profile。