在HotSpot里面,代码执行有两种模式:
- 直接解释指令:这使用的是HotSpot的解释引擎,它采用模板解释,将每一条指令直接翻译成本地代码,而后由硬件直接执行;
- 编译执行:是指,将代码直接翻译成本地代码执行,这就是耳熟能详的JIT编译器,更加准确的说是HotSpot Compiler;
HotSpot采用两种模式混合的设计,主要是出于对性能的考虑。这种性能是用两个指标来衡量的:
- 启动时间:是指,将代码直接编译成本地代码时的时间;
- 稳态性能:是指一条指令被执行的平均速度,一般使用每条指令执行的时间来衡量;
对于任何一个程序,或者说一段代码来说:
执行时间=指令个数稳态性能+启动时间*
因此,如果仅仅使用第一种执行模式,那么启动时间几乎没有,但是稳态性能会比较糟糕。即便是采用模板解释器,每一条指令都要经过取码、译码、翻译、执行几个过程,耗时很长;而在仅仅使用第二种模式的情况下,启动时间长,但是每一条的指令执行,也就是多条本地代码指令的执行,过程相当迅捷。
在综合考虑的情况下,HotSpot就采用分阶段代码执行的模型:
在初开始的阶段,HotSpot直接使用模板解释器执行所有指令。在执行的过程中,HotSpot会收集这些代码的执行情况。在执行一段时间后,HotSpot利用收集到的数据,能够探测到热点代码(也就是HotSpot的含义),而后将这些热点代码进行编译,并执行代码优化,生成了本地代码,往后再次执行这块代码,就是直接执行这个生成的本地代码了:
图中还出现了一个代码缓存和相应的管理器。顾名思义,代码缓存里面缓存的就是编译和优化后的本地代码,在代码缓存里面已经有相应代码的情况下,会直接使用缓存里面的本地代码来执行,而不必再一次编译或者使用解释器。管理器是一个对性能影响很大的组件。因为内存是有限的,HotSpot很可能无法将所有的编译优化之后产生的本地代码都缓存,因此在缓存放满之后就必然要考虑,如何将其中一些代码缓存换出来,而将最新产生的本地代码放进去。
该模型的流程如下: