问题
既然已经内置了JIT编译器,为什么还使用解释器来拖累执行性能呢?
程序启动后,解释器可以马上执行,省去编译时间。即响应速度快。
编译器需要将代码编译为本地代码,需要一定执行时间,但编译完成后,执行效率高。
在此模式下,当Java虚拟器启动时,解释器可以首先发挥作用,而不必等待即时编译器全部编译完成后再执行,这样可以省去许多不必要的编译时间。随着时间的推移,编译器发挥作用,把越来越多的代码编译成本地代码,获得更高的执行效率。
同时,解释执行在编译器进行激进优化不成立的时候,作为编译器的“逃生门”。
如何选择?
热点代码及探测方式
是否启动JIT,需要根据代码被调用执行的频率而定。需要被编译为本地代码的字节码称为热点代码,会被进行深度优化。
多次调用的方法,或在方法体内部循环次数较多的循环体都可以被称为热点代码。由于这种编译方式发生在方法的执行过程中,因此也被称为栈上替换——OSR(On stack Replacement)编译
Hotspot采用的热点探测方式是基于计数器的热点探测。
即为每一个方法都建立两个不同类型的计数器:方法调用计数器,回边计数器。
- 前者统计方法的调用次数
- 后者统计循环体调用次数
方法调用计数器
这个计数器就用于统计方法被调用的次数,它的默认阀值在Client模式下是1500次,在Server模式下是10000次。超过这个阈值,就会触发JIT编译。
阈值可以通过-XX:CompileThreshold
来人为设定。
当一个方法被调用时,会先检查该方法是否存在被JIT编译过的版本,如果存在,则优先使用编译后的本地代码来执行。如果不存在已被编译过的版本,则将此方法的调用计数器值加1,然后判断方法调用计数器与回边计数器值之和是否超过方法调用计数器的阀值。如果已超过阈值,那么将会向即时编译器提交一个该方法的代码编译请求。
热度衰减
方法调用计数器并不统计方法被调用的绝对次数,而是一个相对执行频率——一段时间内方法被调用的次数。
当超过时间限度,如果调用次数不足以交给JIT编译器,则调用计数器会减小一半,称为衰减。这段时间称为半衰周期。
如果想使用绝对次数,则可以用指令-XX:-UseCouterDecay
来关闭热度衰减,如果系统运行时间足够长,绝大部分方法都会被编译成本地代码、
另外,可以使用-XX:CounterHalfLifeTime
参数设置半衰周期的时间,单位是秒。