欢迎来到李先生的博客

深山的鹿,不知归处;万般皆苦,只可自渡。
扩大
缩小

想买保时捷的运维李先生学Java性能之 JIT即时编译器

前言

本文记录日常学习《深入理解Java虚拟机》,不知道为啥感觉看一遍也就过了,喜欢动动手理解理解,这样才有点感觉,静不下心来的时候,看书抄书也可以用这个办法。

 

一、什么是JIT(Just In Time Compiler)即时编译器

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

Java虚拟机规范并没有规定java虚拟机内必须要有即时编译器存在,更没有限定和指导即时编译器应该如何去实现。但是即时编译器的好坏、代码优化程度的高低却是衡量一款商用虚拟机优秀与否的最关键指标之一,它也是虚拟机中最核心最能体现虚拟机水平的部分。

 

二、JVM内置的两个即时编译器

C1编译器:Client Compiler
C2编译器:Server Compiler

 

HotSpot虚拟机,默认采用解释器与其中一个编译器直接配合的方式工作,程序使用哪个编译器,取决于虚拟机运行的模式,HotSPot虚拟机会根据自身版本与宿主机器的硬件性能自动选择运行模式。也可以使用“-client”和“-server”参数去强制指定虚拟机运行在Client模式还是Server模式。

 

[root@ip-10-0-0-90 ~]# java -version
java version "1.8.0_151"
Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode)

[root@ip-10-0-0-90 ~]# java -Xint -version
java version "1.8.0_151"
Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, interpreted mode)

[root@ip-10-0-0-90 ~]# java -Xcomp -version
java version "1.8.0_151"
Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, compiled mode)

 

三、JVM的分层策略

第0层:程序解释执行,解释器不开启性能监控功能(Profiling),可触发第一层编译。
第1层:也称C1编译,将字节码编译为本地代码,进行简单、可靠的优化,如有必要将加入性能监控的逻辑。
第2层(或2层以上):也称C2编译,将字节码编译为本地代码,但是会启用一些编译耗时较长的优化,甚至会根据性能监控信息进行一些不可靠的激进优化。

分层策略在JDK8是默认开启的,C1编译可获取更高的编译速度,C2编译可获取更好的编译质量。

 

四、编译对象和触发条件

编译对象:

1)被多次调用的方法
2)被多次执行的循环体

 

触发条件:

1)基于采样的热点探测
采用这种方法的虚拟机会周期性的检查各个线程的栈顶,如果发现某个或某些方法经常出现在栈顶,那这个方法就是“热点方法”。


2)基于计数器的热点探测
采用这种方法的虚拟机会为每个方法(或代码块)建立计数器,统计方法的执行次数,如果执行次数超过一定的值就认为他是“热点方法”。

 

 

五、JIT编译方式和OSR编译方式

JIT编译方式

方法调用触发的编译,编译器会以整个方法作为编译对象,这种编译也是虚拟机中标准的JIT编译方式。

 

 

 

 

OSR编译方式

循环体调用触发的编译即使是由循环体触发的,但编译器会以整个方法(而不是单独的循环体)作为编译对象。这种编译方式因为编译发生在方法执行过程中,因此形象的称为栈上替换(On Stack Replacement)简称OSR编译,即方法栈帧还在栈上,方法就被替换了。

 

 

 

六、方法计数器和回边计数器

在HotSpot虚拟机中,使用的是基于计数器的热点探测,他为每个方法准备了两类计数器,方法调用计数器(Incocation Counter) 和回边计数器(Back Edge Counter)。这两个计数器都有一个阀值,当计数器超过了这个阀值,就会触发JIT编译。

 

1)方法调用计数器

用于统计方法被调用的次数,默认阀值为Client模式下:1500次,Server模式下10000次;这个阀值可以通过虚拟机参数-XX:CompileThreshold设定。当一个方法被调用时,会先检查该方法是否存在被JIT编译过的版本,如果存在,则优先使用编译后的本地代码来执行。如果不存在已被编译过的版本,则将此方法的调用计数器值加1,然后判断方法调用启与回边计数器值之和是否超过方法调用计数器的阀值,如果超过阀值,那么将会向即时编译器提交该方法的代码编译请求。

在默认情况下,执行引擎不会同步等待编译请求完成,而是继续进入解释器按照解释方式执行字节码,直到提交的请求被编译器编译完成。当编译工作完成后,这个方法的调用入口地址就会被系统自动改写成新的,下一次调用该方法时就会使用已编译的版本。


注:
如果不做任何设置,方法调用计数器统计的并不是方法被调用的绝对次数,而是相对执行频率,即在一段时间之内方法被调用的次数。当超过一定的时间限度,如果方法的调用次数仍然不足以让他提交给即时编译器,那这个方法的调用计数器就会被减少一半,这个过程称为方法调用计数器热度的衰减(Counter Decay),而这段时间就称为此方法统计的半衰周期(Counter Half Life Time)。进行热度衰减的动作是在虚拟机进行垃圾收集时顺便进行的,可以使用虚拟机参数-XX:-UseCounterDecay来关闭热度衰减,让方法计数器统计方法调用的绝对次数,这样只要系统运行时间足够长,绝大部分方法都会被编译成本地代码。另外,可以使用-XX:CounterHalflifeTime参数设置半衰周期的时间,单位为s。

 

2)回边计数器

回边计数器,他的作用是统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为“回边”(back Edge),建立回边计数器统计的目的就是为了触发OSR编译。

 

posted on 2020-10-21 11:04  Captain_Li  阅读(499)  评论(0编辑  收藏  举报

导航