上一章讲了JVM的编译期优化,是指将java源码编译为字节码的过程,这章的运行期优化指的就是将字节码编译成本地机器码的过程。
JVM将字节码编译成本地机器码的过程可以通过解释器和编译器去完成,解释器和编译器不是必须要共存,但是许多主流的商用虚拟机同时包含解释器和编译器。解释器是字节码执行的时候,执行到哪一句,就将对应的字节码解释成本地机器码。而编译器是指现统一将字节码编译成本地机器码,再去执行编译后的机器码。解释器的优势在于程序需要迅速启动和执行时,解释器可以省略编译的时间,在内存限制较大的嵌入式系统中,解释器更为节约内存。编译器的优势在于,将热点代码编译成本地机器码后,可以有效提升执行效率。
HotSpot虚拟机内置了两个即时编译器,分别为Client Compiler和Server Compiler,HotSpot虚拟机会根据自身版本和宿主机器硬件性能自动选择运行模式,用户也可以通过-client或-server参数强制指定运行模式。不管是哪种即时编译器,解释器和编译器搭配使用的方式就是“混合模式”(Mixed Model),全部采用解释方式运行的方式叫作“解释模式”(Interpreted Mode),优先采用编译方式执行的方式叫作“编译模式”(Compiled Mode)。编译器编译的代码会进行优化,为了编译出优化程序很高的代码,编译器还需要解释器帮助其收集性能监控信息,这会影响到解释执行的速度,为了程序启动响应速度和运行效率之间的平衡,HotSpot会逐渐采用分层编译的策略。0层:程序解释执行,解释器不开启性能监控功能,可触发1层编译。1层:将字节码编译为本地代码,进行简单、可靠的优化,必要时可加入性能监控逻辑。2层:将字节码编译为本地代码,会启用一些编译耗时较久的优化,甚至会根据性能监控信息进行一些不可靠的激进优化。
编译器需优化的“热点代码”分类两类:被多次调用的方法、被多次执行的循环体。方法的热点探测判定方式目前有两种:基于采样的热点探测(周期性检查各个线程栈顶,经常出现的方法就为热点方法)、基于计数器的热点探测(对每个方法建立统计执行的计数器,执行次数超过阀值为热点方法)。HotSpot采用的是第二种方法,它为每个方法建立了两类计数器,方法调用计数器(Invocation Counter)和回边计数器(Back Edge Counter),分别统计方法和循环体执行次数。
编译优化的代码比解释执行的代码更为高效的原因在于编译器在生成代码时采用了很多的代码优化技术,如公共子表达式消除(表达式已经计算过,且之后出现的多次表达式中的值并未改变,则无需再次计算,直接用之前计算结果替换)、数组边界检查消除(Java语言中访问数组元素会自动检查上下界,避免越界异常,若程序判定数据很少出现越界情况时,则可以放弃自动检查,改为隐式的异常抛出,节约检查的性能开销)、方法内联(将目标方法代码“复制”到发起调用的方法之中,避免发生真实的方法调用)、逃逸分析(一个对象只在方法或一个线程中用到,则可以定制这个变量的高效优化,如栈上分配、同步消除、标量替换等)