一、Hotspot虚拟机内的即时编译器

  1. 解释器与编译器

Java 最初是通过解释器(Interpreter)进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”(Hot Spot Code)。为了提高热点代码的效率,在运行时,虚拟机会把这些代码编译成与本地平台相关的机器码,并执行各种层次的优化,完成这个任务的编译器称为即时编译器(Just In Time Compiler,JIT)。
解释器与编译器两者各有优势:当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译时间,立即执行。程序运行后,随着时间的推移,编译器逐渐发挥作用,把越来越多的代码编译成本地代码之后,可以获得更高的执行效率。当程序运行环境中内存资源限制较大,可以使用解释执行节约内存,反之可以使用编译执行来提高效率。
同时,解释器可以作为编译器激进优化的一个逃生门,在编译器根据概率选择优化手段,当激进优化不成立的时候,可以通过逆优化退回到解释器状态继续执行。
编译器编译本地代码需要占用程序运行时间,要编译出优化成都更高的代码,所花费的时间可能更长;而且想要编译出优化程度更高的代码,解释器可能还要替编译器收集性能监控信息。
JDK1.7之后,Java默认开启分层策略
第0层:程序解释执行,解释器不开启性能监控功能,可触发第1层编译。
第1层:也称为 C1 编译,将字节码编译为本地代码,进行简单的、可靠的优化,如果有必要加入性能监控的逻辑。
第2层:也称为 C2 编译,将字节码便以为本地代码,但是会启用一些耗时较长的优化,甚至会根据性能监控信息进行一些不可靠的激进优化。

2. 编译对象与触发条件

热点代码一般分为两类:1. 被多次调用的方法。2.被多次执行的循环体。第2种虽然编译动作是由循环体所触发的,但是编译器依然会以整个方法作为编译对象。
热点探测的主要方式有两种:
基于采样的热点探测:采用这种方法的虚拟机会周期性的检测各个线程的栈顶,如果发现某个方法经常出现在栈顶,那这个方法就是热点方法。此方法简单高效,而且可以获取方法的调用关系,但是很难精准地确认一个方法的热度,因为容易受到线程阻塞或别的外界因素的影响而扰乱热点探测。
基于计数器的热点探测:虚拟机为每个方法(甚至是代码块)建立计数器,统计方法的执行次数,如果执行次数超过一定的阈值就认为它是“热点方法”。这种统计方法实现起来麻烦一些,需要为每个方法建立并维护计数器,而且不能直接获取方法的调用关系,但是它的统计结果更为精确严谨。
HotSpot 采用第二种,并为每个方法准备了两类计数器:方法调用计数器(Invocation Counter)和 回边计数器(Back Edge Counter)。




Java统计接口调用方的方法 java统计方法调用次数_编译器


方法调用计数器

Java统计接口调用方的方法 java统计方法调用次数_Java统计接口调用方的方法_02

回边计数器


方法调用计数器存在半衰周期,过了周期之后,计数就会减半,但是回边计数器并没有。回边计数器稍微复杂一些。
当编译器遇到一条回边指令(回边指令是指控制流向后转的指令),当计数超过阈值时会提交一个OSR编译(栈上替换),并且会减少会变计数器的计数,此外回边计数器计数会溢出,溢出的时候会保持他的溢出状态。
默认情况下,无论是方法调用产生即时编译请求,还是OSR编译请求,虚拟机在编译器还未完成之前,都仍然将按照解释方式继续执行,而编译动作在后台执行。