HotSpot团队在即时编译中采用了一些比较经典的优化手段使编译器编译出运行时最优性能的代码。
方法内联
在程序执行过程中,调用一个方法通常要经历入栈和出栈。其执行流程为先将程序执行顺序转移到存储该方法的内存地址,将方法的内容执行完后,再返回到执行该方法前的位置。这种调用流程会产生一定的时间及空间开销。对于一些方法体不大,但调用比较频繁的方法来说,会产生很大的时间和空间的消耗。方法内联就是将被调用方法的代码复制到发起调用的方法之中,避免发生真实的方法调用。方法内联可参考以下代码。
public int add(int i) {
return sub(i + 1);
}
public int sub(int i) {
return i - 1;
}
以上代码可内联为
public int add(int i) {
return sub(i + 1 - 1);
}
jvm在运行过程中会自动识别热点方法,当方法调用次数超过XX:CompileThreshold阈值时会触发方法内联。触发方法内联也有一系列限制,例如对于经常执行的方法小于-XX:MaxFreqInlineSize(默认325字节)会进行方法内联,对于不经常执行的方法小于-XX:MaxInlineSize(默认35字节)会进行方法内联。
方法内联算是编译优化中最重要的优化方法之一,使用方法内联可以消除方法调用的成本,为其他优化手段建立基础。虽然方法内联看着简单只是把被调用代码放入到调用代码段中,但即使编译器在其中发挥了重要作用,否则大部分代码都无法进行方法内联。
逃逸分析
逃逸分析是判断一个对象是否会逃逸到方法或线程之外,如果别的方法或线程无法通过任何途径访问到这个对象,则可能为这个对象进行一些优化。可通过-XX:+DoEscapeAnalysis开启逃逸分析,-XX:-DoEscapeAnalysis 关闭逃逸分析
栈上分配:经过逃逸分析后如果确定一个对象不会被其他线程访问,则会将这个对象分配在栈内存中,占用的内存随着线程结束进行回收,降低垃圾回收器负担。
锁消除:经过逃逸分析后确定一个对象不会被其他线程访问,则会消除对该对象的锁,避免加锁、解锁造成的资源浪费。锁消除可通过--XX:+EliminateLocks开启锁消除,-XX:-EliminateLocks 关闭锁消除。
标量替换:标量指一个数据无法再分解为更小的数据来表示,java中基本数据类型属于标量。如果一个数据可继续分解则成为聚合量。经过逃逸分析后确定一个对象不会被其他线程访问,并且这个对象可以拆散,当程序执行时可能不创建这个对象,改为直接创建它的若干个被这个方法使用到的成员变量替代。使用标量替换可以将对象成员变量分配在栈上或寄存器中,避免创建对象空间。可通过-XX:+EliminateAllocations开启标量替换,-XX:-EliminateAllocations 关闭标量替换。
数组边界检查消除
java程序运行中每次数组元素读写都带有一次隐式条件判定操作,频繁进行条件判定操作会降低系统性能。所以jvm在编译期根据数据流分析确定数组长度,当访问数组时,如果访问的数组下边在数组长度内则不进行边界检查判断。
公共子表达式消除
公共子表达式消除与语言无关,是一项通用的优化。在程序执行过程中如果一个表达式E已经计算过了,并且从先前的计算到现在E中所有变量的值都没发生变化,那么E的这次出现就成为了公共子表达式,对于公共子表达式不会再对其进行计算,只需要将前面计算的结果代替E就可以。