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


大致执行过程如下图所示:
是面向服务端的,并且为服务端性能配置进行了特别调整,是一个充分优化过的高级编译器,几乎能达到 GNU 编译器使用 -O2 参数时的优化强度 。它会执行所有经典的优化动作,比如无用代码消除、循环展开、循环表达式外提、消除公共子表达式、常量传播、基本块重排序等,还会实施一些与 Java 语言特征密切相关的技术,比如范围检查消除、空值检查消除 。另外,还可能根据解释器或提供的性能监控信息,进行一些不稳定的激进优化,如守护内联、分支频率预测等 。后面会挑选部分优化手段进行详细的讲解 。
的寄存器分配器是一个全局图着色分配器,它可以充分利用某些处理器架构上的大寄存器集合 。以即时编译的标准来看,无疑是比较缓慢的,但它的编译速度依然超过传统的静态优化编译器,而且相对于来说代码质量有所提高,可以减少本地代码执行时间,从而抵消了额外的编译时间开销 。
编译优化技术
Java 虚拟机设计团队几乎对代码的所有优化措施都集中在了即时编译器中,因此一般来说,即时编译器产生的本地代码会比 javac 产生的字节码更加优秀 。下面,我们介绍一些虚拟机即时编译器生成代码时采用的代码优化技术 。
优化技术概览
下面列出了虚拟机即时编译器采用的一些优化技术,既有经典编译器的优化技术,也有针对 Java 语言进行的优化技术 。后面我们挑几个重要而且典型的优化进行讲解 。
上面的优化技术看起来都有点“高深莫测”,虽然实现上有些难度,但是大部分理解起来都不难 。下面通过一段 Java 代码的变化过程来展示几种优化技术是如何发挥作用的:
static classs B {int value;final int get() {return value;}}public void foo() {y = b.get();// do somethingz = b.get();sum = y + z;}
上面的代码已经非常简单了,但是仍有许多优化余地 。第一步进行方法内联,方法内联重要性高于其他优化措施,其目的有两个,一是去除方法调用的成本(建立栈帧)等,二是为其他优化建立基础,方法内联膨胀后便于在更大范围上采取后续的优化手段,从而获取更好的优化效果 。内联后的代码如下所示:
public void foo() {y = b.value;// do somethingz = b.value;sum = y + z;}
第二步进行冗余访问消除,假设代码中间注释掉的 do部分不会改变 b.value 的值,那么就可以把 z=b.value 替换成 z=y,这样就不用再去访问对象 b 的局部变量 。如果把 b.value 当成一个表达式,也可以把这项优化看成公共子表达式消除 。优化后代码如下所示:
public void foo() {y = b.value;// do somethingz = y;sum = y + z;}
第三步我们进行复写传播,因为在这段程序中并没有必要使用变量 z,它与变量 y 是完全相等的 。复写传播优化后代码如下所示:
public void foo() {y = b.value;// do somethingy = y;sum = y + y;}
第四步进行无用代码消除,无用代码可能是永远不会被执行的代码,也可能是完全没有意义的代码,因此被形象地称为 Dead Code 。这里 y=y 是无意义的代码,消除之后代码如下所示:
public void foo() {y = b.value;// do somethingy = y;sum = y + y;}
经过四次优化之后,代码比原来精简了很多,执行效率也更高 。接下来,继续学习几种最具有代表性的优化技术,看看它们是如何运作的 。