分层编译
前面提到的 C2,其实还有一个对应的 C1。这里说的 C1、C2 都是即时编译器。
你要是不熟悉 C1、C2,那我换个说法。
C1 其实就是 Client Compiler,即客户端编译器,特点是编译时间较短但输出代码优化程度较低。
C2 其实就是 Server Compiler,即服务端编译器,特点是编译耗时长但输出代码优化质量也更高。
大家常常提到的 JVM 帮我们做的很多“激进”的为了提升性能的优化,比如内联、快慢速路径分析、窥孔优化,包括本文说的“不显示异常堆栈”,都是 C2 搞的事情。
多说一句,在 JDK 10 的时候呢,又推出了 Graal 编译器,其目的是为了替代 C2。
至于为什么要替换 C2,额,原因之一是这样的...
C2 的历史已经非常长了,可以追溯到 Cliff Click 大神读博士期间的作品,这个由 C++ 写成的编译器尽管目前依然效果拔群,但已经复杂到连 Cliff Click 本人都不愿意继续维护的程度。
你看前面我说的 C1、C1 的特点,刚好是互补的。
所以为了在程序启动、响应速度和程序运行效率之间找到一个平衡点,在 JDK 6 之后,JVM 又支持了一种叫做分层编译的模式。
也是为什么大家会说:“Java 代码运行起来会越来越快、Java 代码需要预热”的根本原因和理论支撑。
在这里,我引用《深入理解Java虚拟机HotSpot》一书中 7.2.1 小节[分层编译]的内容,让大家简单了解一下这是个啥玩意。
首先,我们可以使用 -XX:+TieredCompilation
开启分层编译,它额外引入了四个编译层级。
- 第 0 级:解释执行。
- 第 1 级:C1 编译,开启所有优化(不带 Profiling)。Profiling 即剖析。
- 第 2 级:C1 编译,带调用计数和回边计数的 Profiling 信息(受限 Profiling).
- 第 3 级:C1 编译,带所有Profiling信息(完全Profiling).
- 第 4 级:C2 编译。
常见的分层编译层级转换路径如下图所示:
- 0→3→4:常见层级转换。用 C1 完全编译,如果后续方法执行足够频繁再转入 4 级。
- 0→2→3→4:C2 编译器繁忙。先以 2 级快速编译,等收集到足够的 Profiling 信息后再转为3级,最终当 C2 不再繁忙时再转到 4 级。
- 0→3→1/0→2→1:2/3级编译后因为方法不太重要转为 1 级。如果 C2 无法编译也会转到 1 级。
- 0→(3→2)→4:C1 编译器繁忙,编译任务既可以等待 C1 也可以快速转到 2 级,然后由 2 级转向 4 级。
如果你之前不知道分层编译这回事,没关系,现在有这样的一个概念就行了。面试不会考的,放心。
接下来,就要提到一个参数了:
-XX:TieredStopAtLevel=___
看名字你也知道了,这个参数的作用是让分层编译停在某一层,默认值为 4,也就是到 C2 编译。
那我把该值修改为 3,岂不是就只能用 C1 了,那就不能利用 C2 帮我优化异常啦?
实验一波:
果然如此,R大诚不欺我。
关于分层编译,做这样的一个简单的介绍。
学问很大,你要是有兴趣可以去研究研究。
以上。