方法内联

方法内联指的是在编译过程中遇到方法调用时,将目标方法的方法体纳入编译范围之中,并取代原方法调用的优化手段。它不仅可以消除调用本身带来的性能开销,还可以进一步触发更多的优化。

举个例子,如果没有方法内联,当调用getter/setter方法时,程序需要保存当前方法的执行位置,创建并压入用于getter/setter的栈帧,访问字段,弹出栈帧,最后再恢复当前方法的执行,当内联了对getter/setter的方法调用后,上述操作仅剩字段访问。

即时编译器首先解析字节码,并生成IR图,然后在该IR图上进行优化,优化是由一个个独立的优化阶段串联起来的,每个优化阶段都会对IR图进行转换,最后即时编译器根据IR图的节点以及调度顺序生成机器码。

一个相对于IR图好理解的内联形式:

1
2
3
4
5
6
7
public int add(int a, int b , int c, int d){
return add(a, b) + add(c, d);
}

public int add(int a, int b){
return a + b;
}

内联之后

1
2
3
public int add(int a, int b , int c, int d){
return a + b + c + d;
}

不难发现,内敛后少了两次方法的调用,那么就意味着少了两次的方法的入栈。

内联的条件

  1. 热点代码。如果一个方法的执行频率很高就表示优化的潜在价值就越大。这里是根据编译器的编译模式来决定的,如果是客户端模式,则次数是1500,服务端编译模式是10000,该大小可以通过-XX:CompileThreshold来调整
  2. 方法体不能太大。jvm中被内联的方法会编译成机器码放在code cache中。如果方法体太大,则能缓存热点方法就少,反而会影响性能。
  3. 由 -XX:CompileCommand 中的 inline 指令指定的方法,以及由 @ForceInline 注 解的方法(仅限于 JDK 内部方法),会被强制内联。
  4. 如果调用字节码对应的符号引用未被解析、目标方法所在的类未被初始化,或者目标方 法是 native 方法,都将导致方法调用无法内联。