为什么解释器和编译器共存?
首先明确:
当程序启动后,解释器可以马上发挥作用,省去编译的时间,立即执行。编译器要想发挥作用,把代码编译成本地代码,需要一定的执行时间。 但编译为本地代码后,执行效率高。
所以:
尽管JRockit VM中程序的执行性能会非常高效,但程序在启动时必然需要花费更长的时间来进行编译。对于服务端应用来说,启动时间并非是关注重点,但对于那些看中启动时间的应用场景而言,或许就需要采用解释器与即时编译器并存的架构来换取一个平衡点。在此模式下,当Java 虛拟器启动时,解释器可以首先发挥作用,而不必等待即时编译器全部编译完成后再执行,这样可以省去许多不必要的编译时间。随着时间的推移,编译器发挥作用,把越来越多的代码编译成本地代码,获得更高的执行效率。
同时,解释执行在编译器进行激进优化不成立的时候,作为编译器的“逃生门”。
HotSpot JVM的执行方式
当虚拟机启动的时候,解释器可以首先发挥作用,而不必等待即时编译器全部编译完成后再执行,这样可以省去许多不必要的编译时间。并且随着程序运行时间的推移,即时编译器逐渐发挥作用,根据热点探测功能,将有价值的字节码编译为本地机器指令,以换取更高的程序执行效率。
JIT编译器
概念解释
- Java语言的“编译器”其实是一段“不确定”的操作过程,因为它可能是指一个前端编译器(其实叫“编译器的前端”更准确一些)把.java文件转变成.class文件的过程;
- 也可能是指虚拟机的后端运行编译器(JIT编译器)把字节码转变为机器码的过程。
-
还可能是指使用静态提前编译器(AOT编译器,Ahead Of Time Compiler)直接把.java文件编译成本地机器代码的过程。
热点代码及探测方式
当然是否需要启动JIT编译器将字节码直接编译为对应平台的本地机器指令,则需要根据代码被调用指向的频率而定。关于那些需要被编译为本地代码的字节码,也被称之为“热点代码”,JIT编译器在运行时会针对那些频繁被调用的“热点代码”做出深度优化,将其直接编译为对应平台的本地机器指令,以此提升Java程序的执行性能。
- 一个被多次调用的方法,或者是一个方法体内部循环次数较多的循环体都可以被称之为“热点代码”,因此都可以通过JIT编译器编译为本地机器指令。由于这种编译方式发生在方法的执行过程中,因此也被称之为栈上替换,或简称为OSR(On Stack Replacement)编译。
- 一个方法要被调用对少次,或者一个循环体究竟需要执行多少次循环才可以到达这个标准?必然需要一个明确的阈值,JIT编译器才会将这些“热点代码”编译为本地机器指令执行。这里主要依靠热点探测功能。
-
目前HotSpot VM采用的热点探测方式是基于计数器的热点探测。
采用计数器的热点探测,HotSpot VM将会为每一个方法都建立2个不同类型的计数器,分别为方法调用计数器(Invocation Counter)和回边计数器(Back Edge Counter)。- 方法调用计数器用于统计方法的调用次数
- 回边计数器用于统计循环体执行的循环次数
方法调用计数器
- 这个计数器就用于统计方法被调用的次数,它的默认阈值在Client模式下是1500次,在Server模式下是10000次。超过这个阈值,就会出发JIT编译。
- 这个阈值可以通过虚拟机参数-XX:CompileThreshold来人为设定。
- 当一个方法被调用时,会先检查该方法是否存在被JIT编译过的版本,如果存在,则优先使用编译后的本地代码来执行。如果不存在已被编译过的版本,则将此方法的调用计数器值加1,然后判断方法调用计数器与回边计数器值之和是否超过方法调用计数器的阈值。如果超过阈值,那么将会向即时编译器提交一个该方法的代码编译请求。
热度衰减 - 如果不做任何设置,方法调用计数器统计的并不是方法调用的绝对次数,而是一个相对的执行频率,即一段时间之内方法被调用的次数。当超过一定的时间限度,如果当大的调用次数仍然不足以让它提交给即时编译器,那这个方法的调用计数器就会被减少一半,这个过程称为方法调用计数器热度的衰减(Counter Decay),而这段时间就称为此方法统计的半衰周期(Counter Half Life Time)。
- 进行热度衰减的动作是在虚拟机进行垃圾收集时顺便进行的,可以使用虚拟机参数-XX:-UseCounterDecay来关闭热度衰减,让方法计数器统计方法调用的绝对次数,这样,只要系统运行时间足够长,绝大部分方法都会被编译成本地代码。
- 另外,可以使用-XX:CounterHalfLifeTime参数设置半衰周期的时间,单位是秒。
回边计数器
它的作用是统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为“回边”(Back Edge)。显然,建立回边计数器统计的目的就是为了出发OSR编译。
HotSpot VM设置程序执行方式:
- -Xint:完全采用解释器模式执行程序。
- -Xcomp:完全采用即时编译器模式执行程序。如果即时编译器出现问题,解释器会介入执行。
- -Xmind:采用解释器+即时编译器的混合模式共同执行程序。
HotSpot VM中的JIT分类
在HotSpot VM中内嵌有两个JIT编译器,分别为Client Compiler和Server Compiler,但大多数情况下我们简称C1编译器和C2编译器。开发人员可以通过如下命令显示指定Java虚拟机在运行时到底使用哪一种即时编译器。
- -client:指定Java讯虚拟机运行在Client模式下,并使用C1编译器;
**C1编译器会对字节码进行简单和可靠的优化,耗时短。以达到更快的编译速度。 -
server:指定Java虚拟机运行在Server模式下,并使用C2编译器。
**C2进行耗时较长的优化,以及激进优化。但优化的代码执行效率更高。
总结:
- 一般来讲,JIT编译出来的机器码性能比解释器高。
- C2编译器启动时长比C1编译器慢,系统稳定执行以后,C2编译器执行速度远远快于C1编译器。