运行期 【笔记】深入理解 Java 虚拟机:晚期优化( 二 )


基于采样的热点探测:采用这种方法的虚拟机会周期性地检查各个线程的栈顶,如果某个方法经常出现在栈顶,那它就是热点方法 。其优点是简单、高效,还可以获取方法调用关系;缺点是不够精确,容易受到线程阻塞或其他外接因素的影响 。基于计数的热点探测:采用这种方法的虚拟机会为每个方法(甚至是代码块)建立计数器,统计方法执行次数,次数超过一定阈值就认为是热点方法 。这种方法实现起来麻烦,但是其统计结果相对来说更加精确和严谨 。
在虚拟机里使用的是第二种方法,因此它为每个方法准备了两类计数器:方法调用计数器( )和回边计数器(Back edge ) 。在确定虚拟机运行参数的情况下,这两个计数器都有一定的阈值,超过阈值就会触发 JIT 编译 。
方法调用计数器
【运行期【笔记】深入理解 Java 虚拟机:晚期优化】方法调用计数器用于统计方法被调用的次数,其默认阈值在模式下是 1500,在模式下是 10000,该阈值可以通过虚拟机参数 -XX: 来设置 。方法调用计数器与 JIT 编译的交互如下:
默认情况下,方法调用计数器统计的不是方法被调用的绝对次数,而是一段时间内的方法被调用的次数 。当超过一段的时间限度,如果方法的调用次数仍然不足以让它提交给即时编译器编译,那这个方法的调用计数器就会被减少一半 。这个过程称为热度衰减( Decay),这段时间称为方法统计的半衰周期( Half Life Time) 。进行热度衰减的动作是虚拟机在垃圾收集时顺便进行的,可以使用虚拟机参数 -XX:- 来关闭热度衰减 。另外,还可以使用 -XX: 来设置半衰周期的时间,单位是秒 。
回边计数器
回边计数器的作用是统计方法体中循环体代码执行次数,在字节码中遇到遇到控制流向后调整的指令称为回边 。显然,建立回边计数器统计的目的就是为了触发 OSR 编译 。在虚拟机里,通过参数 -XX:tage 来间接调整回边计数器的阈值,其计算公式如下:
模式下,回边计数器阈值计算公式为:方法调用计数器阈值 * tage /模式下,回边计数器阈值计算公式为:方法调用计数器阈值 * (tage - 解释器监控比率) / 100
回边计数器触发即时编译的过程如下所示:
与方法计数器不同,回边计数器没有衰减过程,因此统计的就是绝对次数 。当计数器溢出的时候,它会把方法计数器也调整到溢出状态,它还会把方法计数器也调整到溢出状态,这样下次再进入该方法时就会触发即时编译 。
在虚拟机的源码里,.hpp 文件定义了虚拟机中的内存布局,如下所示:
在这个内存布局中,一行长度为 32bit,从中可以清楚地看到方法调用计数器和回边计数器的位置和长度,还有和 ry 这两个方法的入口 。
编译过程
默认情况下,即时编译是在后台进行的,编译完成之前还是按照解释方式执行,用户可以通过参数 -XX:-n 来禁止后台编译 。
那么在后台编译过程中,做了什么事情呢?和两个编译器的编译过程是不一样的 。是一个简单快速的三段式编译器,主要关注点在于局部优化,放弃了许多耗时的全局优化手段 。
在第一个阶段,一个平台独立的前端将字节码构造成一种高级中间代码(High Level,HIR)表示 。HIR 使用静态单分配的形式来代表代码值,这使得一些在 HIR 之后和之中进行的优化动作更容易实现 。在此之前编译器会在字节码上完成一部分基础优化,如方法内敛、常量传播等 。在第二个阶段,一个平台相关的后端从 HIR 中产生低级中间代码表示(Low Level,LIR)表示 。在此之前,会在 HIR 上完成另一些优化,比如空值检查消除、范围检查消除,以便让 HIR 达到更高效的代码表示形式 。最后阶段,是在平台相关的后端,使用线性扫描算法在 LIR 上分配寄存器,并在 LIR 上做窥孔优化,然后产生机器代码 。