JVM(Java Virtual Machine)即Java虚拟机,Java代码都是在JVM上运行的,所以了解JVM是成为Java高手的毕竟之路。
本系列内容将对JVM的知识进行介绍,是从头学习JVM知识的笔记。
本系列内容根据自己的学习和理解的基础上,并参考《深入理解Java虚拟机》一书介绍的知识所写。如果有写的不对的地方,请各位多多提点。
从头开始学习JVM(八)—— Java的热点与即时编译
Java的热点与即时编译
在JVM中,Java程序最初是通过解释器(Interpreter)进行解释执行的,当虚拟机发现某个方法或代码的运行特别频繁时,就会把它们认定为“热点代码”(Hot Spot Code)。
为了提高热点代码的执行效率,在运行时会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器(Just In Time Compiler,JIT)。
解释器与编译器
主流的Java虚拟机都有解释器与编译器,他们各有优势:
- 当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译时间,立即执行。
- 当程序运行后,随着时间推移,编译器逐渐发挥作用,把代码编译成本地代码之后,可以获取更高的执行效率。
HotSpot中内置了两个即时编译器,分别为Client Compiler(简称C1)和 Server Compiler(简称C2)。默认采用一个解释器和一个编译器配合的方式工作,叫做混合模式(Mixed Mode),用户可以使用“-client”或“-server”参数去强制指定编译器。
只使用解释器的方式叫“解释模式”(Interpreted Mode),只使用编译器的方式叫“编译模式”(Compiled Mode)。可以通过虚拟机的 -version
命令查看相应信息。
同时,解释器还可以作为一个“逃生门”。当编译器进行激进优化不成立时,如加载了新类型后集成结构出现变化、出现了“罕见陷阱”(Uncommon Trap)时,可以通过逆优化(Deoptimization)退回到解释状态继续执行,部分没有解释器的虚拟机(JRockit)也会使用没有激进优化的C1担任逃生门。
即时编译层次
由于即时编译需要耗费时间,或者需要解释器替编译器收集性能监控信息等情况,即时编译采用分层编译(Tiered Compilation),即根据编译器编译、优化的规模和耗时,划分出不同的层次:
- 第 0 层,程序解释执行,解释器不开启性能监控功能(Profiling),可触发第 1 层编译。
- 第 1 层,也称为C1编译,将字节码编译为本地代码,进行简单、可靠的优化,如有必要加入性能监控的逻辑。
- 第 2 层(或2层以上),也称为C2编译,也是将字节码编译为本地代码,但是会启用一些编译耗时较长的优化(编译优化程度更高),甚至会进行一些不可靠的激进优化。
实施分层编译后,C1和C2可以同时工作,许多代码可能会被多次编译。用C1来获取更高的编译速度,用C2来获取更好地编译质量。
编译对象与触发条件
在运行过程中会被即时编译器编译的“热点代码”有两类:
- 被多次调用的方法。一个方法被调用、执行的次数较多。
- 被多次执行的循环体。方法体内存在循环多次的循环体,虽然该方法可能只被调用一次或几次,但是循环体却是重复执行多次的。
第一种情况,编译器会以整个方法作为编译对象,这也是JVM中的标准JIT编译方式。
第二种情况,依然会以整个方法作为编译对象,但是编译发生在方法执行的过程中(循环方法体内),因此形象的称为栈上替换(On Stack Replacement,OSR编译,即方法栈帧还在栈上,但是方法被替换了)。
次数是判断为热点代码的判定条件,判断一段代码是不是热点代码的行为就叫“热点探测”(Hot Spot Detection),目前主要的热点探测方式有两种:基于采样的热点探测,基于计数器的热点探测。
基于采样的热点探测
基于采样的热点探测(Sample Based Hot Spot Detection):采用这种方式虚拟机会周期性地检查各个线程的栈顶,如果某个方法经常出现在栈顶,即为“热点方法”。
这种方式的好处是实现简单、高效,容易获取方法调用关系。缺点是很难精确确认热度,也可能是因为线程阻塞等外界因素导致堆积在栈顶。