概念
JVM在解释执行字节码的过程中会收集代码块或方法的执行频率用于判断该代码块或方法是否为热点代码,如果被判定为热点代码则这些代码在运行时会被编译并优化成与本地平台相关的可被cpu直接执行的机器码。这种编译过程称为JIT(即时编译),执行JIT的模块被称为JIT编译器。
谁被编译
- 被多次调用的方法
- 被多次执行的循环体,编译行为由循环体触发但编译的对象仍然是该循环体涉及到的方法。这种编译方式被称为栈上替换-OSR(由于方法还在执行过程中即方法还在线程栈帧上发生的编译)。
触发条件(热点探测方法)
- 基于计数器的热点探测(Counter Based Hot Spot Detection)
虚拟机会为每个方法(或每个代码块)建立计数器,统计执行次数,如果超过阀值那么就是热点代码。缺点是维护计数器开销。 - 基于采样的热点探测(Sample Based Hot Spot Detection)
虚拟机会周期性检查各个线程的栈顶,如果某个方法经常出现在栈顶,那么就是热点代码。缺点是不精确。 - 基于踪迹的热点探测(Trace Based Hot Spot Detection)
Dalvik中的JIT编译器使用这种方式
HOTSPOT虚拟机(基于计数器的热点探测)
- 方法调用计数器(Invocation Counter)
- 默认触发阈值:在Client模式下是1500次,Server是10000次,可以通过参数
-XX:CompileThreshold
来设定 - 当一个方法被调用时会首先检查是否存在被JIT编译过得版本,如果存在则使用此本地代码来执行;如果不存在,则将方法计数器+1,然后判断“方法计数器和回边计数器之和”是否超过阀值,如果是则会向编译器提交一个方法编译请求
- 默认情况下,执行引擎并不会同步等待上面的编译完成,而是会继续解释执行。当编译完成后,此方法的调用入口地址会被系统自动改写为新的本地代码地址
- 还有一点,热度是会衰减的,也就是说不是仅仅+,也会-,热度衰减动作是在虚拟机的GC执行时顺便进行的
- 默认触发阈值:在Client模式下是1500次,Server是10000次,可以通过参数
- 回边计数器(Back Edge Counter)
- 只有执行到大括号”}”时才算+1
- 默认阀值:Client下13995,Server下10700
- 它的调用逻辑和方法计数器差不多,只不过遇到回边指令时+1、超过阀值时会提交OSR编译请求以及这里没有热度衰减
编译器(Hotspot JVM)
- Client Compiler (C1)
相对于C2编译来说有比较高的启动速度但对编译后的代码优化比较初级,不过相对于解释执行也有一定的执行性能提升。在混合编译模式中,当jvm以client模式启动时使用该编译器。 - Server Compiler(C2)
编译优化程度较深,编译后代码执行效率高,但是编译耗时和内存消耗(memory footprint)较高,因此启动速度较慢。比较适用于长时间运行的服务端程序。在混合编译模式中,当jvm以server模式启动时使用该编译器。
编译模式(Hotspot JVM)
- 混合模式(Mixed Mode)
HotSpot默认采用解释器和其中一个编译器直接配合的方式工作,使用哪个编译器取决于虚拟机运行的模式,HotSpot会根据自身版本和宿主机器硬件性能自动选择模式,用户也可以使用“-client”或”-server”参数去指定。 - 解释模式(Interpreted Mode)
以纯解释执行的方法运行 - 分层编译(tier)
jdk1.8及之后默认开启分层编译,如要禁止则在启动参数减伤-XX:-TieredCompilation
。
分层编译被启用时,C1和C2编译器会同时工作,同一份代码可能会被多次编译,编译级别逐渐增加。
五个编译级别:- level 0 - 解释执行
- level 1 - C1编译
- level 2 - C1编译,需要收集运行时性能数据
- level 3 - level 2 + MDO
- level 4 - C2 编译优化
编译过程
编译过程是在后台线程(daemon)中完成的,可以通过参数“-XX:-BackgroundCompilation”来禁止后台编译,但此时执行线程就会同步等待编译完成才会执行程序。
- C1编译器是一个简单快速的三段式编译器,主要关注“局部性能优化”,放弃许多耗时较长的全局优化手段
过程:class -> 1. 高级中间代码 -> 2. 低级中间代码 -> 3. 机器代码 - C2是专门面向服务器应用的编译器,是一个充分优化过的高级编译器,几乎能达到GNU C++编译器使用-O2参数时的优化强度