运行期优化

追求性能的路上!


早期的Java程序是通过解释器进行执行的,只有在运行期,JVM发现hotpot code时,才会将这些代码编译成和本地相关的机器码,并进行优化,实现者就称为即时编译器,但是即时编译器不是JVM必需的部分

像主流厂商,如Hotpot、J9等,都是解释器和编译器共存的状态,两者各有优势。想程序需要快速启动和执行的时候,解释器可以先发挥作用,随着时间推移,编译器逐渐发挥作用,将更多的代码编译成本地代码,以获取更高的效率;反过来,某些时候,编译器的优化不好,就会退回到解释状态继续执行,解释器就作为“逃生门”的角色

Hotpot内置了两个即时编译器:Client Compiler和Server Compiler,Hotpot根据自身版本和宿主机器的硬件性能自动选择运行模式(Client模式和Server模式,这两种都成为Mixed模式),当用户使用参数-Xint强制使用解释器时,就进入解释模式。当使用Xcomp时,就进入编译模式

1
2
3
4
5
6
7
8
9
10
11
12
PS C:\Program Files\Java\jdk1.8.0_201\bin> java -version
java version "10.0.1" 2018-04-17
Java(TM) SE Runtime Environment 18.3 (build 10.0.1+10)
Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.1+10, mixed mode)
PS C:\Program Files\Java\jdk1.8.0_201\bin> java -Xint -version
java version "10.0.1" 2018-04-17
Java(TM) SE Runtime Environment 18.3 (build 10.0.1+10)
Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.1+10, interpreted mode)
PS C:\Program Files\Java\jdk1.8.0_201\bin> java -Xcomp -version
java version "10.0.1" 2018-04-17
Java(TM) SE Runtime Environment 18.3 (build 10.0.1+10)
Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.1+10, compiled mode)

为了在程序响应速度和运行效率之间达到最佳的平衡,Java 7中,Hotpot采用了分层编译的策略,并默认开启

  • 第0层:程序解释执行。解释器不开启性能监控功能时,触发第1层编译
  • 第1层:C1编译,将字节码编译为本地代码,进行简单优化,加入性能监控
  • 第2层:C2编译,会启动一些耗时较长的优化

多次调用的方法和多次执行的循环体会触发编译器编译,编译器对两种情况都是选择以整个方法为编译对象,判断代码是不是热点代码叫做热点探测

  • 基于采样的热点探测:JVM周期性检查各个线程的栈顶,如果发现经常出现在栈顶,就认为是热点代码,但这种方法会因为线阻塞而受干扰
  • 基于计数器的热点探测:JVM为每个方法建立计数器,统计方法的执行次数,又分为方法调用计数器和回边计数器。计数器有一个阈值,只有当超过阈值时,才会触发JIT编译,阈值可通过-XX:CompilerThreshold设定
    方法计数器执行流程

从图中看到,在方法执行期间,存在替换方法的可能性,我们称这个过称为栈上替换(OSR,On-Stack Replacement),OSR是一种在运行时替换正在运行的函数/方法的栈帧的技术。但它是手段,不是目的——是出于某种目的需要在运行时替换栈帧。使用OSR最常见的目的就是在一个函数/方法的执行过程中,在执行引擎的不同优化层级之间切换,可以是从低优化层级向高优化层级切换,也可以反过来。这也就隐含了一个假设——这个执行引擎有多个层级的优化

方法计数器统计的并不是绝对次数,而是相对频率,因为计数器有一个热度衰减的机制,如果超过一段时间,次数就会减少一半,这个周期成为半衰周期,可以使用-XX:UseCounterDecay关闭热度衰减,-XX:ConuterHalfLifeTime设置半衰周期

回边计数器则是统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为回边(类似于汇编中的loop),回边计数器的目的就是为了触发OSR编译,回边计数器没有热度衰减,统计的就是绝对次数,而且当回边计数器溢出时,还会设置方法计数器的值也为溢出

默认情况下,编译动作都是早后台进程中进行,但设置-XX:-BackgroundCompilation可以禁止后台编译,执行线程想虚拟机提交编译后将会一直等待,知道编译完成再开始执行