HotSpot虚拟机即时编译器4大问题解决-即时编译器的学习
1.为何HotSpot虚拟机要使用解释器与编译器并存的架构?
2.为何HotSpot虚拟机要实现两个不同的编译器?
3.程序何时使用解释器执行?何时使用编译器执行?
4.哪些程序代码会被编译成本地代码?如何编译为本地代码?
1.为何HotSpot虚拟机要使用解释器与编译器并存的架构?
尽管不是所有的java虚拟机都采用解释器和编译器并存的架构,但许多主流的商用虚拟机,如HotSpot,J9等,都同时包含解释器和编译器。解释器与编译器两者各有优势:当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译时间,立即执行。在程序运行后,随着时间的退役,编译器逐渐发挥作用,把越来越多的代码编译成本地代码只有可以获取更好的性能。当程序运行环境中内存资源限制较大,可以使用解释器执行节约内存,反之可以使用编译执行来提升效率。同时,解释器还可以作为编译器激进优化时的一个逃生门,让编译器根据概率选择一些大多数时候都能提升运行速度的优化手段,当激进优化的假设不成立,会通过逆优化退回到解释状态继续执行。在整个架构中,解释器和编译器经常配合工作,这就使为什么要使用并存架构的原因。
2.为何HotSpot虚拟机要实现两个不同的编译器?
在HotSpot VM中内嵌有两个JIT编译器,分别为Client Compiler和Server Compiler,但大多数情况下我们简称为C1编译器和C2编译器。开发人员可以通过如下命令显式指定Java虚拟机在运行时到底使用哪一种即时编译器,如下所示:
-client:指定Java虚拟机运行在Client模式下,并使用C1编译器;
-server:指定Java虚拟机运行在Server模式下,并使用C2编译器。
除了可以显式指定Java虚拟机在运行时到底使用哪一种即时编译器外,默认情况下HotSpot VM则会根据操作系统版本与物理机器的硬件性能自动选择运行在哪一种模式下,以及采用哪一种即时编译器。简单来说,C1编译器会对字节码进行简单和可靠的优化,以达到更快的编译速度;而C2编译器会启动一些编译耗时更长的优化,以获取更好的编译质量。不过在Java7版本之后,一旦开发人员在程序中显式指定命令“-server”时,缺省将会开启分层编译(Tiered Compilation)策略,由C1编译器和C2编译器相互协作共同来执行编译任务。不过在早期版本中,开发人员则只能够通过命令“-XX:+TieredCompilation”手动开启分层编译策略。编译层次包括:
第0层,程序解释执行,解释器不开启性能监控,可触发第1层。
第1层,也称为C1编译,将字节码编译为本地代码,进行简单,可靠的优化,如有必要将加入性能监控的逻辑。
第2层,C2编译,将字节码编译为本地代码,但是会启用一些编译耗时较长的优化。
用C1获取更高的便以速度,用C2获取更好的编译质量,这就是原因。
程序何时使用解释器执行?何时使用编译器执行?
一般的代码都是使用解释器执行,当代码变为热点代码的时候就会被JIT执行。
热点代码有两类:被多次调用的方法和被多次执行的循环体。
而判断一个方法为热点代码的量化标准,有两种方法:
>
1) 基于采样的热点探测。周期性的探测各线程的栈顶方法,找出出现频率高的。这种方法并不准备,结果容易受干扰(如线程等待)
>
2) 基于计数器的热点探测。为每个方法保留一个计数器,统计被调用的次数,若超过某个“阀值”,则断定为热点方法。因此,在每个方法入口,都会判断是否有翻译过的代码,若没有,则计数器加一,判断是否超过“阀值”,若超过,则为热点代码,保留其编译后的代码。流程如下:
方法调用计数器client默认1500次,server默认10000次,可以通过参数-XX:CompileThreshold来设定。调用方法时,会先判断是否存在编译过的版本,如果有则调用该版本,否则计数器加1,然后看方法调用计数器和回边计数器之和是否超过方法调用计数器的阈值。超过,则提交编译请求。
方法调用计数器并不是统计方法调用绝对次数,而是一个相对执行频率,超过一定时间,如果方法调用次数不足以让它提交给编译器,则计数器就会被减少一半,这种现象称为热度衰减(Counter Decay),进行热度衰减的动作是在垃圾回收时顺便进行的,而这段时间就被称为半衰周期(Counter Half Life Time)可用-XX:-UseCounterDecay来关闭热度衰减,用-XX:CounterHalfLifeTime来设置半衰时间。
回边计数器用于统计方法中循环体的执行次数。字节码遇到控制流向后跳转 的指令成为回边。建立回边计数器统计的目的就是为了触发OSR编译(栈上替换)。回边的控制参数有:
-XX:BackEdgeThreshold,-XX:OnStackReplacePercentage。
哪些程序代码会被编译成本地代码?如何编译为本地代码?
哪些代码:当向编译器提交编译请求的那些代码。
简单来说C1和C2两个编译过程不同。
C1:简单快速三段式编译,主要关注局部的优化,放弃全局优化。
C2:专门面向服务端的典型应用并未服务端的性能配置特别调整过的编译器。