一、执行引擎
介绍:
- 执行引擎是Java虚拟机核心的组成部分之一。
- 执行引擎的作用:
- 由于代码经过前端编译器后生成字节码文件,JVM将装在字节码文件到内部,JVM中只有自己的执行引擎才"解析"对应的指令集,然后为翻译成机器指令(解释器)或者是将翻译成机器指令后再加入缓存(JIT编译器)。
- 执行引擎的工作过程:
- 依赖于PC寄存器,当PC寄存器指向哪条指令时,执行引擎就去操作该指令对应的操作。
- 当在执行的过程中,遇到局部变量表中的对象引用时,会根据引用访问堆中对象的实例以及根据对象头来访问对应位置的数据。
解释器和JIT编译器:
橙色部分由前端编译器来完成,例如平常使用的ide或者javac。
当执行引擎要执行字节码文件的时候有两种选择:一种是使用解释器、另外一种就是使用JIT编译器。
- 解释器:
- 功能:配合PC计数器将字节码文件逐行解释成机器指令后执行。
- 优点:不需要等待,拿到字节码文件即可立即进行解释。
- 缺点:若遇到热点代码时效率低下。
- JIT编译器:
- 功能:将一段代码编译成本地代码,当再次执行到这段代码时,可以从代码缓存中取出,无需再对其逐条解析。
- 优点:遇到热点代码执行效率高。
- 缺点:需要一段时间讲这段代码执行翻译存入缓存。
为什么HotSpot JVM采用解释器与编译器相结合?
若只采用解释器代码执行效率低下,尤其是web项目。若只采用JIT编译器的话会将字节码执行翻译进缓存,而这段时间只能会让用户等待。所以两个相结合时,当JVM启动的时候,解释器可以先发挥作用,而不用等待JIT编译器全部翻译好了再去执行,可以提高程序总的执行效率,当随着时间的推移,JIT会把越来越多的字节码编译成本地代码,效率会逐渐提高。
另一方面是JIT编译器相当于使用解释器为自己的后路:因为JIT编译器在勘测到热点代码这段才会对其执行翻译缓存,若不是热点代码还是使用解释器进行解释的。
我们可以通过参数"-Xint"去设置只采用解释器执行程序,也可以通过参数"-Xcomp"设置只采用编译器去执行,当然如果出现问题解释器还是会介入。最后也就是默认的"-Xmixed"参数设置共同执行程序。
热点探测功能:
- 一个很多次被调用的方法,或者是一个方法体内部循环多次的循环体都可以被称作热点代码,JIT可以将这些代码,由于这些编译方式发生在方法执行的过程中,所以也被称为栈上替换,或者OSR(on stack repalcement)编译。
- Hotsopt JVM目前才用的热点探测方式是基于计数器的热点探测。
- 主要有两种热点探测计时器,分别是:方法调用计数器(统计方法调用的次数)和回边计数器(统计循环体调用的次数)
- 执行流程:当一个方法被调用的时候,会先检查这个方法是否存在JIT编译过的版本,若存在则直接拿本地代码执行,若不存在则将方法调用计数器+1,然后判断方法调用计数器和回边计数器之和有没有到达阈值,若到达了则使用JIT编译器编译成本地代码执行后存入本地代码缓存,若没有达到还是使用解释器进行解释方法执行。在Client模式下这个阈值默认是1500次、而在Server模式下默认是10000次,可以通过参数"-XX:ComplieThreshold"来进行设置阈值。
- 热度衰减:当一段时间内,如果方法调用没有到达阈值,则会将计数器的次数减少一半,我们可以通过参数"-XX:-UseCounterDecay"来设置是否开启热度衰减,也可以通过"-XX:CounterHanlfLifeTime"来设置半衰周期的时间(单位是秒)。
C1与C2编译器:
在Hotspot JVM中内嵌两个编译器,就是C1与C2编译器。
- C1编译器:
- 可以通过参数"-client"设置在Client模式下使用。
- 特点:对字节码进行简单和可靠的优化,耗时短。
- 优化方式:主要有方法内联、去虚拟化、冗余消除。
- 方法内联:将引用的函数代码编译到引用点里,减少栈帧的生产,减少参数传递以及跳转过程。
- 去虚拟化:对唯一的实现类进行内联,与方法内联类似。
- 冗余消除:在运行期间把一些不会执行的代码折叠掉。
- C2编译器:
- 可以通过参数"-server"设置在Server模式下使用。
- 特点:对字节码进行耗时较长的优化、以及激进优化,使得代码执行效率更高。
- 优化方式:依赖于逃逸分析。
- 标量替换:用标量值替换聚合对象的属性值。
- 栈上分配:对于未逃逸的对象分配在栈上。
- 同步消除:消除不必要的同步操作,通常指synchronized。
分层编译策略:
- 程序解释执行可以触发C1编译,将字节码编译成机器码,可以进行简单的优化,也可以加上性能监控,C2编译会根据性能监控信息进行激进优化。
- 在Java 7后,一旦指定是server模式下默认开启分层编译策略,就是C1和C2会共同协作执行编译任务。
扩展:
- 在JDK 10后,Hotspot又加入了新的即时编译器:Graal编译器
- 目前还是测试阶段,但效果与C2编译器持平。
- 在JDK 9 后引入了AOT编译器(静态提前编译器):
- 在运行前,将字节码转化为机器码的编译器。
- 好处是减少Java运用在刚开始的时候运行慢的问题,坏处是并不能达成一次编译到处运行的功能,而且降低了链接过程的动态性。