JIT编译器
概念解释:
- Java语言的"编译器"其实是一段"不确定"的操作过程,因为它可能是指一个
前端编译器
(其实叫"编译器的前端"更准确一些)把.java文件转变成.class文件的过程。 - 也可能是指虚拟机的
后端运行期编译器
(JIT编译器,Just In Time Compiler)把字节码转变成机器码的过程。 - 还可能是指使用
静态提前编译器
(AOT 编译器,Ahead Of Time Compiler)直接把.java文件编译成本地机器代码的过程。
前端编译器: Sun的Javac、Eclipse JDT中的增量式编译器(ECT)。
JIT 编译器: HotSpot VM的C1、C2编译器。
AOT 编译器: GNU Compiler for the Java (GCJ)、Excelsior JET。
热点代码及探测方式
当然是否需要启动JIT编译器将字节码直接编译为对应平台的本地机器指令,则需要根据代码被调用执行的频率
而定。关于那些需要被编译为本地代码的字节码,也被称之为"热点代码"
,JIT编译器在运行时会针对那些频繁被调用的"热点代码"做出深度优化
,将其直接编译为对应平台的本地机器指令,以此提升Java程序的执行性能。
-
一个被多次调用的方法,或者是一个方法体内部循环次数较多的循环体都可以被称之为"热点代码"
,因此都可以通过JIT编译器编译为本地机器指令。由于这种编译方式发生在方法的执行过程中,因此也被称之为栈上替换,或简称为OSR(On Stack Replacement)编译
。 - 一个方法究竟
要被调用多少次
,或者一个循环体究竟需要执行多少次循环才可以达到这个标准?必然需要一个明确的阈值,JIT编译器才会将这些"热点代码"编译为本地机器指令执行。这里主要依靠热点探测功能
。 目前HotSpot VM所采用的热点探测方式是基于计数器的热点探测。
- 采用基于计数器的热点探测,HotSpot VM将会为每一个方法都建立2个不同类型的计数器,分别为方法调用计数器(Invocation Counter)和回边计数器(Back Edge Counter)。
- 方法调用计数器用于统计方法的调用次数
- 回边计数器则用于统计循环体执行的循环次数
方法调用计数器
- 这个计数器就用于统计方法被调用的次数,它的默认阈值在Client模式下是1500次,在Server模式下是10000次。超过这个阈值,就会触发JIT编译。
- 这个阈值可以通过虚拟机参数
-XX:CompileThreshold
来人为设定。 - 当一个方法被调用时,会先检查该方法是否存在被JIT编译过的版本,如果存在,则优先使用编译后的本地代码来执行。吐过不存在已被编译过的版本,则将此方法的调用计数器值加1,然后判断方法调用计数器与回边计数器之和是否超过方法调用计数器的阈值。如果已超过阈值,那么将会向即时编译器提交一个该方法的代码编译请求。
热度衰减
- 如果不做任何设置,方法调用计数器统计的并不是方法被调用的绝对次数,而是一个相对的执行频率,即
一段时间之内方法被调用的次数
。当超过一定的时间限度
,如果方法的调用次数仍然不足以让它提交给即时编译器编译,那这个方法的调用计数器就会被减少一半
,这个过程称为方法调用计数器热度的衰减(Counter Decay)
,而这段时间就称为此方法统计的半衰周期(Counter Half Life Time)
。 - 进行热度衰减的动作是虚拟机进行垃圾收集时顺便进行的,可以使用虚拟机参数
-XX:-UseCounterDecay
来关闭热度衰减,让方法计数器统计方法调用的绝对次数,这样,只要系统运行时间够长,绝大部分方法都会被编译成本地代码。 - 另外,可以使用
-XX:CounterHalfLifeTime
参数设置半衰周期的时间,单位是秒。
回边计数器
它的作用是统计一个方法中循环体代码执行的次数
,在字节码中遇到控制流向后跳转的指令称为"回边"(Back Edge)。显然,建立回边计数器统计的目的就是为了触发OSR编译。