《深入理解java虚拟机——JVM高级特性与最佳实践》阅读笔记 编译晚期优化

java 程序通过解释器进行解释执行,如果发现热点代码(多次调用的方法或者多次执行的循环体),则会使用即时编译器将这些部分编译为与本地相关的机器码,并进行各种优化。

许多主流商用虚拟机都包括解释器与编译器,解释器有助于启动时节省编译时间,立即执行。编译器在程序启动后,不断将代码编译为本地代码,提高执行效率。解释器还可以在编译器进行激进的优化尝试出现问题时,作为“逃生门”,通过逆优化退回解释状态继续执行。

对于由循环体触发的编译操作,编译器依然将整个方法作为编译对象。这种方式发生在方法执行过程中,所以又被称为(栈上替换)

虚拟机热点代码判断方式
方式名称判断细节
基于采样的热点探测虚拟机周期性检查各个线程栈顶,将经常出现在栈顶的方法判定为热点方法。简单高效而且容易获得调用关系,但由于受外界因素影响而很难精准地确认方法的热度。
基于计数器的热点探测虚拟机为每个方法(或代码块)建立计数器,统计执行次数,超过阈值则判定为热点方法。并不能获得调用关系,但是结果更加精确严谨

 

HotSpot 中设置了两个即时编译器,Client Compiler 和 Server Compiler(简称C1和C2)。一般采用解释器与其中一个编译器直接配合的方式工作,称为“混合模式”。用户也可以自行设置“解释模式”(编译器不参与工作)或者“编译模式”(优先采用编译方式执行,但是在无法编译时解释器会介入),而由于编译本地代码需要占用程序运行时间,而且解释器要为编译器收集性能监控器信息,所以对执行速度会有影响。为了达到平衡,HotSpot 启用分层编译的策略。

分层编译的层次结构
层次具体操作
0解释执行,解释器不开启性能监控功能,可触发第1层编译
1C1编译,将字节码编译为本地代码,进行简单可靠的优化。必要时加入性能监控逻辑
2C2编译,与C1不同的是,会启用编译耗时较长的优化,甚至是进行不可靠的激进优化

实施分层编译后,Client Compiler 与 Server Compiler 将同时工作,许多代码可能会被多次编译。Client Compiler 获得更高编译速度,Server Compiler 获得更高编译质量,解释执行时也不再收集性能监控信息。

HotSpot 采用的是计数器的方式,并准备了两种计数器:方法调用计数器回边计数器

调用计数器:

阈值:Client 模式下1500,Server 模式下10000

方法被调用时,先查看是否存在被编译过的版本,存在则优先使用编译过的本地代码。不存在则将计数器值+1,然后判断调用计数器与回边计数器的和是否超过调用计数器的阈值。超过阈值请求即时编译器执行编译。如果不进行任何设置,执行引擎将继续进入解释器解释字节码直到提交的请求被编译完成。然后方法的调用入口地址会被改为新的,下一次调用时会使用已编译的版本。

不进行任何设置时,调用计数器并不是统计绝对次数,一段时间内相对的执行频率。超过一定时间但调用次数仍不足以达到提交给即时编译器的要求时,则该方法的调用计数器减半(计数器热度的衰减),该时间也被称为此方法统计的半衰周期。衰减动作在回收垃圾时顺便执行。

回边计数器

作用是统计方法体中控制流向后跳转的指令出现次数,也就是“回边”出现次数(空循环不会被统计)。该计数器目的在于触发OSR编译

 Client 模式下,阈值计算公式为:方法调用计数器阈值*OSR比率(默认933)/100,全部采取默认值时,结果为13995

Server 模式下,阈值计算公式为:方法调用计数器阈值*OSR比率(默认933)-解释器监控比率/100,全部采取默认值时,结果为10700

整体过程与调用计数器相似,不同在于超过阈值时会发出 OSR请求,并降低计数器的值以便继续在解释器中执行循环。而且没有计数热度衰减的过程,统计的就是循环执行的绝对次数。当计数器溢出时计数器的值也会被调整到溢出状态,下次进入方法就执行标准编译过程。

默认情况下,虚拟机在编译器未完成之前都将按照解释的方式执行,编译动作则在后台线程进行。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值