目 录CONTENT

文章目录

即时(JIT)编译器编译对象与触发条件(方法调用计数器与回边计数器)

Administrator
2022-11-15 / 0 评论 / 3 点赞 / 2118 阅读 / 6224 字

JIT编译对象与触发条件

JIT编译器在运行时会针对那些频繁被调用的“热点代码”做出深度优化,将其直接编译为对应平台的本地机器指令,以此提升Java程序的执行性能。“热点代码”指的又是什么?

编译对象

被JIT所编译的“热点代码”即是JIT的编译对象,主要有以下两类:

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

对于这两种情况,编译的目标对象都是整个方法体,而不会是单独的循环体。
被多次执行的循环体,尽管编译动作是由循环体所触发的,热点只是方法的一部分,但编译器依然必须以整个方法作为编译对象,只是执行入口(从方法第几条字节码指令开始执行)会稍有不同,编译时会传入执行入口点字节码序号(Byte Code Index,BCI)。这种编译方式因为编译发生在方法执行的过程中,因此被很形象地称为栈上替换”(On Stack Replacement,OSR),即方法的栈帧还在栈上,方法就被替换了。

触发条件

要知道某段代码是不是热点代码,需不需要触发即时编译,这个行为称为“热点探测”(HotSpot Code Detection),其实进行热点探测并不一定要知道方法具体被调用了多少次,目前主流的热点探测判定方式有两种,分别是:

  • 基于采样的热点探测(Sample Based Hot Spot Code Detection)。采用这种方法的虚拟机会周期性地检查各个线程的调用栈顶,如果发现某个(或某些)方法经常出现在栈顶,那这个方法就是“热点方法”。基于采样的热点探测的好处是实现简单高效,还可以很容易地获取方法调用关系(将调用堆栈展开即可),缺点是很难精确地确认一个方法的热度,容易因为受到线程阻塞或别的外界因素的影响而扰乱热点探测。
  • 基于计数器的热点探测(Counter Based Hot Spot Code Detection)。采用这种方法的虚拟机会为每个方法(甚至是代码块)建立计数器,统计方法的执行次数,如果执行次数超过一定的阈值就认为它是“热点方法”。这种统计方法实现起来要麻烦一些,需要为每个方法建立并维护计数器,而且不能直接获取到方法的调用关系。但是它的统计结果相对来说更加精确严谨

HotSpot虚拟机中使用的是第二种基于计数器的热点探测方法,为了实现热点计数,HotSpot为每个方法准备了两类计数器方法调用计数器(Invocation Counter)回边计数器 (Back Edge Counter)。这两个计数器都有一个明确的阈值,计数器阈值一旦溢出,就会触发即时编译。

方法调用计数器

  • 虚拟机参数-XX:CompileThreshold
  • 这个计数器就是用于统计方法被调用的次数,默认阈值在客户端模式下是1500次,在服务端模式下是10000次(64位系统下默认就是server)。
  • 当一个方法被调用时虚拟机会先检查该方法是否存在被即时编译过的版本,如果存在,则优先使用编译后的本地代码来执行。如果不存在已被编译过的版本,则将该方法的调用计数器值加一,然后判断方法调用计数器与回边计数器值之和是否超过方法调用计数器的阈值。一旦已超过阈值的话,将会向即时编译器提交一个该方法的代码编译请求
  • 如果没有做过任何设置,执行引擎默认不会同步等待编译请求完成,而是继续进入解释器按照解释方式执行字节码,直到提交的请求被即时编译器编译完成。当编译工作完成后,这个方法的调用入口地址就会被系统自动改写成新值,下一次调用该方法时就会使用已编译的版本了,整个即时编译的交互过程
    image-1668504824512

image-1668506598258

注意:

  • 方法调用计数器统计的并不是方法被调用的绝对次数(永久叠加的次数),而是一个相对的执行频率,是一段时间之内方法被调用的次数
  • 当超过一定的时间限度,如果方法的调用次数仍然不足以让它提交给即时编译器编译,那该方法的调用计数器就会被减少一半,这个过程被称为方法调用计数器热度的衰减(Counter Decay),而这段时间就称为此方法统计的半衰周期(Counter Half Life Time),进行热度衰减的动作是在虚拟机进行垃圾收集时顺便进行的,可以使用虚拟机参数-XX:-UseCounterDecay来关闭热度衰减,让方法计数器统计方法调用的绝对次数,这样只要系统运行时间足够长,程序中绝大部分方法都会被编译成本地代码。另外还可以使用-XX:CounterHalfLifeTime 参数设置半衰周期的时间,单位是

回边计数器

回边计数器的作用是统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令就称为“回边(Back Edge)",
很显然建立回边计数器统计的目的是为了触发栈上的替换编译。
关于回边计数器的阈值,虽然HotSpot虚拟机也提供了一个类似于方法调用计数器阈值-XX:CompileThreshold 的参数-XX:BackEdgeThreshold供用户设置,但是当前的HotSpot虚拟机实际上并未使用此参数,我们必须设置另外一个参数-XX:OnStackReplacePercentage来间接调整回边计数器的阈值,其计算公式有如下两种。

  • 虚拟机运行在客户端模式下,回边计数器阈值计算公式为:

方法调用计数器阈值(-XX:CompileThreshold)* OSR比率(-XX:OnStackReplacePercentage)/ 100。
其中-XX:OnStackReplacePercentage 默认值为933,如果都取默认值,那客户端模式虚拟机的回边计数器的阈值为13995。

  • 虚拟机运行在服务端模式下,(64位下就是server模式)回边计数器阈值的计算公式为:

方法调用计数器阈值(-XX:CompileThreshold)*(OSR比率(-XX:OnStackReplacePercentage)- 解释器监控比率(-XX:InterpreterProfilePercentage))/ 100。
其中-XX:OnStackReplacePercentage默认值为140,-XX:InterpreterProfilePercentage 默认值为33,如果都取默认值,那服务端模式虚拟机回边计数器的阈值为10700。

当解释器遇到一条回边指令时,会先查找将要执行的代码片段是否有已经编译好的版本,如果有的话,它将会优先执行已编译的代码,否则就把回边计数器的值加一,然后判断方法调用计数器与回边计数器值之和是否超过回边计数器的阈值。当超过阈值的时候,将会提交一个栈上替换编译请求,并且把回边计数器的值稍微降低一些,以便继续在解释器中执行循环,等待编译器输出编译结果,整个执行过程如图所示。
image-1668506359153

注意

与方法计数器不同,回边计数器没有计数热度衰减的过程,因此这个计数器统计的就是该方法循环执行的绝对次数。当计数器溢出的时候,它还会把方法计数器的值也调整到溢出状态,这样下次再进人该方法的时候就会执行标准编译过程

HotSpot VM设置程序执行方式

缺省情况下HotSpot VM是采用解释器与即时编译器并存的架构,当然开发人员可以根据具体的应用场景,通过命令显式地为Java虚拟机指定在运行时到底是完全采用解释器执行,还是完全采用即时编译器执行。如下所示:

  • -Xint:完全采用解释器模式执行程序:
  • -Xcomp:完全采用即时编译器模式执行程序。如果即时编译出现问题,解释器会介
  • 入执行。
  • -Xmixed:采用解释器+即时编译器的混合模式共同执行程序。
3

评论区