五个级别
前文讲过,编译器分c1和c2两种,c1适合客户端,启动速度快,但是编译后的代码质量不高。C2适合服务端,启动速度慢,占用内存高,但是编译后的代码质量高,执行效率高。在JDK8以前,使用-client可以激活C1编译器,-server可以激活C2编译器。但是JDK8及以后的版本这个就没用了。
那有没有中间方案呢?JDK7开始,出现了混合模式编译,也就是分层编译技术。分层编译技术不仅Java有,Java的竞争对手.Net也有分层编译技术。
Java的分层编译将编译定义为五个级别:
级别 | 策略 |
---|---|
0 | 解释器执行 |
1 | 无任何监控模式的C1编译器 |
2 | 带方法调用和回边计数器的C1编译器 |
3 | 完全监控模式(MDO)的C1编译器 |
4 | C2编译器 |
对于level 1~3,很多博文没解释清楚,我解释一下。解释器是肯定带方法调用和回边计数器的。但是C1编译器可以带,也可以不带。所以level 1,level 2和level 3。性能最高的是level 1,因为不监控任何数据,性能最差的是level 3。所以他们的性能是level 4 > level 1 > level 2 > level 3 > level 0。但是因为level 1没有任何监控,所以它不可能有触发机制到达level 4,除非反优化,level 1将是终态。
更需要注意的编译级别的切换不是整个虚拟机的级别切换,而是某个方法的级别切换。在虚拟机运行过程中,一个程序流程,调用了很多方法,这里面有些方法是解释器运行,有些是编译器运行。所以编译级别是方法的属性,也不是线程的属性,更不是虚拟机的属性。
级别切换
通常情况:
0
→
3
→
4
0 \to 3 \to 4
0→3→4
C2队列满:
0
→
2
→
3
→
4
0 \to2\to 3 \to 4
0→2→3→4。这里我解释一下。因为level 3的目的是完全监控,然后用监控数据去进行C2编译。因为C2队列满了,所以不仅level4的方法不能再增加,level 3的方法也不能继续增加,所以level 0就直接编译为level 2。等C2队列空闲的时候,level 2的方法又调整为level 3。
C2队列满,已经在队列里的变成2:
3
→
2
3\to2
3→2。这里我解释一下,因为C2队列满了,所以已经在队列里的level 3临时改变目标,编译为level 2。
禁用了C2或者方法体过于简单:
0
→
2
→
1
0 \to 2 \to 1
0→2→1
禁用了C2或者方法体过于简单:
0
→
3
→
1
0 \to 3 \to 1
0→3→1
解释器完全监控模式:
0
→
4
0 \to4
0→4
反优化:
(
1
,
2
,
3
,
4
)
→
0
(1,2,3,4)\to0
(1,2,3,4)→0
为此,我画了一张图: