运行期 【笔记】深入理解 Java 虚拟机:晚期优化

文章目录编译过程编译优化技术
概述
在部分商用虚拟机(Sun 、IBM J9)中,Java 程序最初是通过解释器解释执行的,当虚拟机发现有个方法或代码块运行特别频繁时,就会把这些代码认定为“热点代码” 。为了提高热点代码的执行效率,虚拟机会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器被称为即时编译器(Just In Time,简称 JIT 编译器) 。
虚拟机内的即时编译器
本节介绍虚拟机内的即时编译器的运作过程,同时还要解决以下几个问题:
为何虚拟机要使用解释器和编译器并存的架构?为何虚拟机要实现两个不同的即时编译器?程序何时使用解释器执行?何时使用编译器执行?哪些程序代码会被编译为本地代码?如何编译为本地代码?如何从外部观察即时编译器的编译过程和编译结果? 解释器与编译器
尽管不是所有的 Java 虚拟机都采样解释器与编译器并存的价格,但是许多主流的虚拟机,比如 Sun 、IBM J9,都同时包含解释器与编译器 。解释器与编译器有各自的优势:当程序需要快速启动时,解释器可以发挥作用,省去编译时间立即执行 。在程序运行后,随着时间的推移,编译器逐渐发挥作用,把越来越多的代码编译成本地代码后,可以获取更高的执行效率 。
当程序运行环境的内存资源限制较大时,使用解释器执行节省内存,反之可以使用编译执行提升效率 。同时,解释器还可以作为编译器激进优化时的一个“逃生门”,让编译器根据概率选择一个大多数时候都能提升运行速度的优化手段,当激进优化的假设不成立时,比如加载了新类后类型继承结构出现变化,出现“罕见陷阱”时可以通过逆优化退回到解释状态继续执行 。因此,在虚拟机中解释器和编译器经常配合工作,如下图所示:
虚拟机内置了两个即时编译器:和,简称 C1 编译器和 C2 编译器 。默认采用解释器和其中一个编译器直接配合的方式工作,具体使用哪个编译器,取决于虚拟机工作的模式,用户可以使用 - 参数或 - 参数指定虚拟机的工作模式,还可以使用 -Xint 强制虚拟机运行于“解释模式” 。
由于即时编译器编译本地代码需要占用程序运行时间,要编译出优化程度更高的代码,所需时间会更长 。同时,解释器还要替编译器收集性能监控信息,这对解释执行速度也有影响 。为了在程序启动响应速度与运行效率之间达到最佳平衡,虚拟机又引入了分层编译的策略 。分层编译根据编译器编译、优化的规模与耗时,划分为不同的编译层次,包括:
第 0 层,程序解释执行,不开启性能监控功能,可触发第 1 层编译 。第 1 层,称为 C1 编译,将字节码编译为本地代码,并进行简单可靠的优化,如有必要将加入性能监控逻辑 。第 2 层,称为 C2 编译,也是将字节码编译为本地代码,但是会进行耗时较长的优化,甚至会根据性能监控信息进行一些不完全可靠的激进优化 。
实施分层编译后,和会同时工作,许多代码可能会被编译多次,用获得更快的编译速度,用获取更好的编译质量,在解释执行的时候也无需再承担收集性能监控信息的任务 。
编译对象与触发条件
在运行过程中,会被即时编译器编译的热点代码有两类:
被多次调用的方法 。被多次执行的循环体 。
这两种情况,编译器都会编译整个方法 。因为编译发生在方法执行过程中,因此形象地称之为栈上替换(On Stack,简称 OSR,即方法栈帧还在栈上,方法就被替换了) 。
判断一段代码是不是热点代码,是否需要触发即时编译,这样的行为称为热点探测(Hot Spot ),热点探测方式主要有两种: