10分钟理解Java JIT 即时编译

简单理解 JIt 编译

Jit编译的作用:在部分的商用虚拟机中,Java 程序最初是通过解释器( Interpreter )进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁的时候,就会把这些代码认定为“热点代码”。为了提高热点代码的执行效率,在运行时,即时编译器(Just In Time Compiler )会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化。

这里简述了JIT编译的作用,但是我们还需要知道JIT编译在什么时候启动。在这之前我们先看一下Java类的执行过程,如下图:
10分钟理解Java JIT 即时编译
图中的描述已经很清晰了只是其中引入了一个概念:热点代码,那么什么是热点代码呢?
在运行过程中会被即时编译的“热点代码”有两类,即:

  • 被多次调用的方法
  • 被多次执行的循环体

对于第一种,编译器会将整个方法作为编译对象,这也是标准的JIT 编译方式。对于第二种是由循环体出发的,但是编译器依然会以整个方法作为编译对象,因为发生在方法执行过程中,称为栈上替换。

现在我们知道了什么是热点代码,那么jvm是怎么识别到这些热点代码的或者说如何像垃圾回收那样找到这些“垃圾”并触发编译的?
判断一段代码是否是热点代码,是不是需要出发即时编译,这样的行为称为热点探测(Hot Spot Detection),探测算法有两种,分别为:

  1. 基于采样的热点探测(Sample Based Hot Spot Detection):虚拟机会周期的对各个线程栈顶进行检查,如果某些方法经常出现在栈顶,这个方法就是“热点方法”。好处是实现简单、高效,很容易获取方法调用关系。缺点是很难确认方法的reduce,容易受到线程阻塞或其他外因扰乱。
  2. 基于计数器的热点探测(Counter Based Hot Spot Detection):为每个方法(甚至是代码块)建立计数器,执行次数超过阈值就认为是“热点方法”。优点是统计结果精确严谨。缺点是实现麻烦,不能直接获取方法的调用关系。

HotSpot 使用的是第二种-基于计数器的热点探测,并且有两类计数器:方法调用计数器(Invocation Counter )回边计数器(Back Edge Counter )

这两个计数器都有一个确定的阈值,超过后便会触发 JIT 编译,具体阈值此处不再说那么详细,来看一下这两个计数器的作用以及概念:

  • 方法调用计数器
    为每个方法建立计数器,每多一次调用,那么计数器数值+1,但是存在特殊情况,由于阈值是一段时间内调调用次数即代表频率,所以在一段时间内计数器达不到阈值,也就是无法使其交给编译器处理,那么此时便将计数器值减半,这个过程称为方法调用计数器热度的衰减(Counter Decay),而这段时间就成为此方法的统计的半衰周期( Counter Half Life Time)进行热度衰减的动作是在虚拟机进行垃圾收集时顺便进行的,可以使用虚拟机参数 -XX:CounterHalfLifeTime 参数设置半衰周期的时间,单位是秒。整个 JIT 编译的交互过程如下图:
    10分钟理解Java JIT 即时编译
  • 回边计数器
    作用是统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为“回边”( Back Edge )。显然,建立回边计数器统计的目的就是为了触发 OSR 编译。关于这个计数器的阈值, HotSpot 提供了 -XX:BackEdgeThreshold 供用户设置,但是当前的虚拟机实际上使用了 -XX:OnStackReplacePercentage 来简介调整阈值, 执行过程如下图:
    10分钟理解Java JIT 即时编译