同步消除(Synchronization Elimination)
线程同步本身是一个相对耗时的过程,如果逃逸分析能够确定一个变量不会逃逸出线程,无法被其他线程访问,那么这个变量的读写肯定就不会有竞争, 对这个变量实施的同步措施也就可以安全地消除掉。
VM 参数:
-XX:+EliminateLocks 开启锁消除(JDK1.8 默认开启)
公共子表达式消除(Common Subexpression Elimination)
公共子表达式消除是一个非常经典的、普片应用各种编译器的优化技术。
在同一个作用域下,如果程序中有公共的计算表达式,多次出现参与运算的结果相同,计算结果为 E,那么后面的表达式直接用 E 替代结果。
一个表达式已经被计算过了,并且从先前计算到现在的计算所有的变量值都没有发生变化,称为 E 为公共表达式。对于这种表达式,我们就不需要重新计算了,只需要使用之前计算过的表达式结果替代 E 。如果这种代码优化局限与程序代码块内,可称为局部公共表达式消除(Local Common Subexpression Elimination)。如果这种优化的范围覆盖了多个代码块,那就称为全局公共子表达式消除(Global Common Subexpression Elimination)。
举个例子:
int d = (c * b) * 12 + a + (a + c * b);
虚拟机的即时编译器会进行代码优化(因为在代码块内 c、b 的之没有改变,编译器认为没必要多次计算),上面的表达式会被优化为:
int d = E * 12 + a + (a + E);
在这个时候编译器也可能会进行代数简化(Algebraic Simplification)。
int d = E * 13 + a + a
经过简化后再计算起来就可以节省一些 CPU 的时间。
数组边界检查消除(Array Boounds Checking Elimination)
数组边界检查消除(Array Boounds Checking Elimination)是及时编译器优化的经典技术。对于 Java 而言本来就是一门动态安全的语言,比如我们访问一个数组 foo[i] 的时候:
- 先去检查数组是否为空,如果 foo 为空返回
NullPointException
异常。
- 如果数组越界会得到
ArrayIndexOutOfBoundsExecption
异常。
其实 JVM 为我们做了非常多的隐含条件判断。
但是对于这些操作,为了安全肯定是要做的,但是如果对 foo[i] 循环取值,如果编译器通过分析发现可以判定循环变量的取值永远在区间 [0, foo.length] 之内,那么在这个循环中我们就可以吧这个那个数组的上下界限检查消除掉。
举个例子:
if (i >= 0 && i < foo.length) { return foo[i]; }
优化过后:
return foo[i];
除此之外还有一种隐式异常处理,Java 空指针检查和算数运算除数为 0 的检查都采用了这种方案。 举个例子:
if (foo != null) { return foo.value; } else { throw new NullPointException(); }
在隐式异常优化之后:
try { return foo.value } catch (segment_fault) { uncommon_trap(); }
虚拟机会注册一个 Segment Fault 的信号异常处理器,当 foo 不为空的时候,访问 value 是没有任何开销的。但是如果 foo 为空,会进入异常处理涉及到进程从用户态转到内核态的处理,处理完成后又回到用户态,同样一次操作比判空检查慢的多。
HptSpot 会更具运行期收集到的监控信息自动选择最优方案。
其他优化:自动装箱消除(Autobox Elimination)、安全点消除 (Safepoint Elimination)、消除反射(Dereflection)等。
参考文档
- 《深入理解 JVM 虚拟机》周志明