深入理解java虚拟机(18):运行期优化

当虚拟机发现某个代码块频繁访问,就认为是热点代码,为了提高热点代码的执行效率,在运行时虚拟机将会把这些代码编译成与本地机器代码相关的机器码,并进行各种层次的优化,完成这个任务的就是即时编译器。

1、HotSpot虚拟机内的即时编译器

1)为何HotSpot虚拟机要使用解释器与编译器并存的架构

2)为何HotSpot虚拟机要使用两个不同的即时编译器

3)程序何时使用编译器?何时使用解释器

4)哪些程序代码会被编译成本地代码?如何编译成本地代码

5)如何从外部观察即时编译器编译过程和编译结果

编译器和解释器各有优势,当程序需要快速启动时,解释器发挥作用,省去编译时间,当运行后随着时间推移,编译器把越来越多的代码编译成本地代码获取更高的效率。

HotSpot内置两个即时编译器,一个client Compiler,一个server compiler,hotspot会根据宿主的主机配置自动选择一个与解释器相互配合。为了编译出优化程度更高的代码,解释器可能还要替编译器收集性能监控信息,这对解释器运行效率也有影响。为了让程序启动相应速度和运行效率达到平衡,Hotspot采用分层编译。

如下

0层解释器工作,解释器不开启性能监控,可以触发1层编译。1层编译将字节码编译成本地代码,进行简单可靠的优化,如果有必要加入性能监控逻辑。2层编译将字节码编译成本地代码,启用一些耗时间较长的优化措施,也会根据性能监控开启一些不可靠的激进优化。

2、编译对象和触发条件

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

如何判断是否是热点

1)基于采样的热点探测,采用这个方法的虚拟机会周期性的检查各个线程栈顶,经常出现的就是热点方法。优点简单高效,缺点容易被阻塞或别的外界因素的影响而扰乱热点探测

2)基于计数器的热点探测,采用这个方法的虚拟机需要为每一个方法建立一个计数器,统计方法执行次数,如果超过一定阈值就认为它是热点方法。优点实现精确和严谨,缺点实现麻烦。

hotspot选择第二种,有两个程序计数器,一个是方法调用计数器,另一个是回边计数器。默认情况client是1500次,server是10000这个参数可以用-XX:CompileThreshold来人为设定。当一个方法被调用时,先判断是否被JIT编译过,如果存在本地代码就优先使用本地代码执行。如果没有计数器加一,如果计数器超过阈值就请求编译器编译该方法。当编译完成,这个方法调用入口地址,就会被系统改成新的地址,下一次执行就是新的版本。

 

 

如果不做任何设置,方法调用计数器不是统计的总数,统计的是频率,即一段时间内,如果超出一段时间计数器值减半。这个时间被称为半衰期,可以通过XX:-UseCounterDecay来关闭,可以通过XX:CounterHalfLifeTime参数设置,单位是秒。

回边计数器,统计循环体执行次数。字节码中遇到控制流向后跳转的指令,称为回边。显然建立回边计数器统计的目的就是为了触发osr编译。设置参数为XX:BackEdgeThreshold设置,实际虚拟机没有使用该参数,我们需要设置的另外一个参数-XX:OnStackReplacePercentage来间接调整回边计数器的阈值。

虚拟机在client模式下回边计数器阈值计算公式:CompileThreshold * osr(OnStackReplacePercentage /100),OnStackReplacePercentage 默认933,都取默认值13995。虚拟机server运行模式下计算公式为CompileThreshold * osr(OnStackReplacePercentage /100)-解释器监控比例(InterpreterProfilePercentage/100),OnStackReplacePercentage 默认140,InterpreterProfilePercentage默认值33,如果都取默认值server模式下阈值是10700

 

 与方法计数器不同的是,回边计数器没有计数热度衰减的过程。

3、编译过程

不管是即时编译请求,osr编译请求都是在后台线程中编译完成,如果通过参数XX:-BackgroundCompilation来禁止后台编译,那么达到了jit编译条件后,执行线程执行提交编译请求后就会一直等待,直到编译完毕输出本地代码后才开始执行本地代码。

client compiler编译过程如下,第一阶段,一个平台无关的前端将字节码转换成高级中级代码表示(High Level Intermediate Representation,HIR)。HIR使用静态单分配的形式来表示代码值,这可以使得一些HIR的构造过程中和之后进行的优化动作更容易实现。在此之前编译器会做一些基础优化,如方法内联,常量传播等优化将会在字节码被构造hir之前完成。

第二阶段,一个平台有关的从HIR中产生低级中间代码表示,在此之前会在hir上完成另外的一些优化,如空值检查消除,范围检查消除等,以便让HIR达到更高效的代码表示形式。

最后阶段采用线性表扫描法,在LIR上分配寄存器,并在LIR上做窥空优化,然后产生机器代码。

 

 4、查看及分析编译结果

openJDK网站下载FastDebug版本的JDK

package org.xiaofeiyang.classloader;

/**
* @author: yangchun
* @description:
* @date: Created in 2019-12-02 16:21
*/
public class FastDebug {
public static final int NUM = 15000;
public static int doubleValue(int i){
for(int j=0;j<100000;j++){
}
return i*2;
}
public static long calcSum(){
long sum = 0;
for(int i=0;i<=100;i++){
sum = doubleValue(i);
}
return sum;
}
public static void main(String[] args){
for(int i=0;i<NUM;i++){
calcSum();
}
}
}
编译运行并且使用参数-XX:+PrintCompilation虚拟机将编译成本地代码的方法名称答应出来,带%号说明触发了回边计数器OSR编译。加上
-XX:+PrintInling要求虚拟机输出方法内联信息。

 

posted on 2019-12-02 16:49  清浊  阅读(330)  评论(0编辑  收藏  举报