及时编译
通常情况下,Java程序最初都是被编译为字节码,通过解释器进行解释执行,解释执行能够获得更好的启动时间。某些被频繁执行的方法或者代码块,会被JVM认定为“热点代码”。在运行时JVM会把这些热点代码编译成与本地平台相关的机器码,并且进行各种层次的优化,以提高执行效率。完成这个任务的编译器称为即时编译器(JIT编译器)。
Java 虚拟机是根据方法的调用次数以及循环回边的执行次数来触发即时编译的。
热点代码:
运行过程中会被即时编译器编译的“热点代码”有两类:
1、被多次调用的方法。
2、被多次执行的循环体。
两种情况,编译器都是以整个方法作为编译对象。 这种编译方法因为编译发生在方法执行过程之中,因此形象的称之为栈上替换(On Stack Replacement,OSR),即方法栈帧还在栈上,方法就被替换了。
分层编译
HotSpot内置了C1编译器和C2编译器。默认情况下,JVM采取解释器和其中一个编译器直接配合的运行模式,编译器的选择,根据自身的版本以及宿主机器的硬件性能自动选择。此外,用户也可以通过JVM参数强制JVM的运行模式。
用Client Complier获取更高的编译速度,用Server Complier 来获取更好的编译质量。为什么提供多个即时编译器与为什么提供多个垃圾收集器类似,都是为了适应不同的应用场景。
Java 7 引入了分层编译(对应参数 -XX:+TieredCompilation)的概念,综合了 C1 的启动性能优势和 C2 的峰值性能优势。
分层编译将 Java 虚拟机的执行状态分为了五个层次。为了方便阐述,我用“C1 代码”来指代由 C1 生成的机器码,“C2 代码”来指代由 C2 生成的机器码。五个层级分别是:
(1)解释执行;
(2)执行不带 profiling 的 C1 代码;
(3)执行仅带方法调用次数以及循环回边执行次数 profiling 的 C1 代码;
(4)执行带所有 profiling 的 C1 代码;
(5)执行 C2 代码;
其中 1 层的性能比 2 层的稍微高一些,而 2 层的性能又比 3 层高出 30%。这是因为 profiling 越多,其额外的性能开销越大。
不同层次的编译路径如下: