深入了解JVM——运行期优化

Posted 在咖啡里溺水的鱼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入了解JVM——运行期优化相关的知识,希望对你有一定的参考价值。

本文为 《深入理解Java虚拟机》第十一章内容的学习笔记,部分内容经过二次加工。若对相关知识感兴趣,推荐购书深入阅读。若认为文章涉嫌侵权,请联系作者及时删除。
本作品采用 知识共享署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议 (CC BY-NC-SA 3.0 CN) 进行许可 。非商业性质转载请注明作者和出处,禁止商业性质转载。
开源创造世界

概述

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

HotSpot虚拟机内的即时编译器

解释器与编译器

解释器与编译器各有优势:当程序需要迅速启动和执行时,解释器首先发挥作用,省去编译的时间,立即执行。成勋运行后,随着时间的推移,编译器把原来越多的代码编译成本地代码,获得更高的执行效率。内存资源限制较大,可以使用解释执行节约内存,反之可以使用编译执行来提升效率。解释器还可以作为编译器激进优化的逃生门,让编译器根据概率选择一些大多数时候都能提升运行速度的优化手段,当激进优化的假设不成立时可以通过逆优化 Deoptimization退回到解释状态继续执行。在整个虚拟机执行架构中,解释器与编译器经常配合工作。

HotSpot中内置了两个即时编译器,Client Compiler 和 Server Compiler,建成C1、C2 。主流HotSpot虚拟机中,默认采用解释器与其中一个编译器直接配合的方式。Hotspot会根据宿主机性能自动选择模式,也可以使用-client -server参数强制指定。

解释器与编译器搭配使用的方式在虚拟机中称为 混合模式 Mixed Mode, 可以通过java -Xint 强制虚拟机运行于 解释模式 Interpreted Mode,全部代码都是用解释方式执行。可以通过java -Xcomp 强制虚拟机运行于 编译模式 Compiled Mode,有限采用编译方式执行程序,解释器在编译无法进进行的情况下介入执行过程。 可以通过虚拟机的java -version 命令显示这三种模式。

为了在程序启动响应速度与运行效率之间达到平衡,HotSpot虚拟机会逐渐启用分层编译 Tiered Compilation 的策略。
- 第0层,程序解释执行,解释器不开器性能监控 Profiling 功能,可处罚第一层编译
- 第1层,C1编译,将字节码编译为本地代码,进行简单、可靠的优化,有必要将加入性能监控的逻辑
- 第2层及以上,C2编译,将字节码编译为本地代码,会启动一些编译耗时较长的优化,可能会根据性能监控信息进行一些不可靠的激进优化。

实施分层编译后,Client Compiler和Server Compiler会同时工作,许多代码可能会被多次编译,用Client Compiler获取更高的编译速度,用Server Compiler获取更好的编译质量,在解释执行时也无需再承担手机性能监控信息的任务。

编译对象与触发条件

在运行过程中会被即时编译器编译的 热点代码 有两类
- 被多次调用的方法
- 被多次执行的循环体

两种情况,编译器都会以方法作为编译对象。针对第二种情况,因为编译发生在方法执行过程之中,因此称为 栈上替换 On Stack Replacement OSR编译。

判断一段代码是不是热点代码,是否需要触发即时编译,这样的行为称为热点探测 Hot Spot Detection,主要的热点探测判定方式有两种:
- 基于采样的热点探测 Sample Based Hot Spot Detection:周期性的检查各个线程的栈顶,如果发现某些方法经常出现在栈顶,就会被认定为 热点方法。有点是实现简单、高校,容易获取方法调用关系,缺点是不精确,容易受到线程阻塞或别的外界因素的影响而扰乱热点探测。
- 基于计数器的热点探测 Counter Based Hot Spot Detection:为每个方法或代码块建立计数器,统计方法的执行次数,如果执行次数超过一定的阈值就认定为 热点方法。缺点是麻烦,且不能直接获取到方法的调用关系,但统计结果更加精确和严谨。

HotSpot中使用的是第二种,它为每个方法准备了两类计数器:方法调用计数器 Invocation Counter 和 回边计数器 Back Edge Counter。

方法调用计数器

在确定虚拟机运行参数的前提下,两个计数器都有一个确定的阈值,计数器超过阈值溢出了,就会触发JIT编译。Client模式下默认阈值是1500次,Server模式下10000次,阈值可以通过 -XX:CompileThreshold来人为设定。

当一个方法被调用时会先检查该方法是否存在被JIT编译过的版本,存在时使用编译后的本地代码执行,不存在时将此方法的调用计数器值加1,然后判断方法调用计数器与回边计数器之和是否超过方法调用计数器的阈值。

不做任何设置的情况下,执行引擎不会同步等待编译请求完成。方法调用计数器统计的并不是方法被调用的绝度次数,而是一个相对的执行频率,一定时间中如果方法的调用次数仍然不足以让它提交给JIT编译,那这个方法的调用计数器就会被减少一半,这个过程称为方法调用计数器热度的衰减,这段时间称为方法统计的半衰周期 Counter Half Life Time。热度衰减的动作时虚拟机GC时进行的,可以使用-XX:-UseCounterDecay关闭热度衰减。可以使用-XX:CounterHalfLifeTime参数设置半衰周期的时间,单位秒。

回边计数器

统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为 回边 Back Edge。回边计数器统计的目的就是为了触发OSR编译。可以使用-XX:OnStackReplacePercentage来调整回边计数器的阈值。
- Client模式下:方法调用计数器阈值 CompileThreshold * OSR比率 OnStackReplacePercentage / 100。OnStackReplacePercentage默认值为933,默认情况下Client模式虚拟机的回边计数器的阈值为13995.
- Server模式下:方法调用计数器阈值 CompileThreshold * (OSR比率 OnstackReplacePercentage - 解释器监控比率 InterpreterProfilePercentage)/ 100。OnStackReplace 默认值140,InterpreterProfilePercentage默认33,回边计数器默认阈值为10700

当超过阈值时,回提交一个OSR编译请求,并把回边计数器的值降低,以便继续在解释器中执行循环。

回边计数器没有技术热度衰减。

编译过程

默认设置下,虚拟机在代码编译器还未完成之前,都仍然按照解释方式执行,编译动作在后台的编译线程中执行。可以通过参数 -XX:BackgroundCompilation 禁止后台编译,禁止后台编译后,一旦达到JIT的编译条件,执行线程向虚拟机提交编译请求后会一直等待,知道编译过程完成后再开始执行编译器输出的本地代码。

Client Compiler

是一个简单的三段式编译器,主要关注点在于局部优化,放弃大部分耗时较长的全局优化手段。

第一阶段,一个平台独立的前端将字节码构造成一种高级中间代码表示 HIR High-Level Intermediate Representation。HIR使用静态单分配 SSA Static Single Assignment 的形式来表示代码值,这可以使得一些在HIR的构造过程之中和之后进行的优化动作更容易实现。

第二阶段,一个平台相关的后端从HIR中产生第几中间代码表示 LIR Low-Level Intermediate Representation ,在此之前会在HIR上完成另外一些优化

第三阶段,在平台相关的后端使用线性扫描算法 Linear Scan Register Allocation在LIR上分配寄存器,并在LIR上坐窥孔 Peephole 优化,然后产生机器代码。

Server Compiler

专门面向服务端的典型应用并为服务端的性能配置特别调整。

会执行所有经典的优化动作

比较缓慢,单编译速度超过传统的静态优化编译器,并且相比Client Compiler编译输出的代码质量有所提高,可以减少本地代码的执行时间,从而抵消额外的编译时间开销。

查看及分析即时编译结果

部分参数需要Debug或FastDebug版虚拟机的支持

要知道某个方法是否被编译过,可以使用参数-XX:+PringCompilation要求虚拟机在编译时将被编译成本地代码的方法名称打印出来。-XX:+PrintInlining要求虚拟机输出方法的内敛信息。

虚拟机提供了一组通用的反汇编接口,可以介入各种平台下的反汇编适配器来使用。为虚拟机安装了反汇编适配器之后,可以使用-XX:PrintOptoAssembly(Server) 或 -XX:+PrintLIR(Client)输出比较接近最终结果的中间代码表示。对于Produt版本的虚拟机,需要加入参数-XX:UnlockDiagnosticVMOption打开虚拟机诊断模式后才能使用。

可以使用-XX:PrintCFGToFile(Client)或-XX:PrintIdealGraphFile(Server)令虚拟机将编译过程中各个阶段的数据输出到文件中。然后使用Java HotSpot Client Compiler Visualizer(Client)或Ideal Graph Visualizer(Server)打开这些数据文件进行分析。

编译优化技术

优化技术概览

以上是关于深入了解JVM——运行期优化的主要内容,如果未能解决你的问题,请参考以下文章

深入了解JVM虚拟机8:Java的编译期优化与运行期优化

JVM总结:晚期(运行期)优化

晚期(运行期)优化

JVM晚期运行期优化

JVM -- 运行期优化;JIT

JVM -- 运行期优化;JIT