方法内联
当一个线程调用方法时,就会把栈帧压入到栈中。压栈和出栈都是有Cpu,内存,时间开销的。
Jvm会自动识别热点方法,并发起方法内联。
方法内联就是把目标方法的代码复制到发起调用的方法之中,避免发生真实的方法调用。可以避免压栈和出栈的操作。提高效率。
方法内联:
public class InlineTest {
private static int add1(int x1,int x2,int x3,int x4){
return add2(x1, x2) + add2(x3, x4);
}
private static int add2(int x1, int x2) {
return x1 + x2;
}
//内联后
private static int add(int x1,int x2,int x3,int x4){
return x1+ x2 + x3+ x4;
}
}
方法内联的条件
-
方法体足够小
- 热点方法:如果方法体小于325字节会尝试内联,可用-XX:FreqInlineSize修改大小
- 非热点方法:如果方法体小于35字节会尝试内联,可用-XX:MaxInlineSize修改大小
-
被调用方法运行时的实现被可以唯一确定
- static方法、private方法以及 final 方法,JIT可以唯一确定具体的实现代码
- public 的实例方法,指向的实现可能时自身、父类、子类的代码(因为有多态的实现),当且仅当JIT能够唯一确定方法的具体实现时,才有可能完成内联
使用方法内联的注意点
根据以上两个条件,可以总结出使用方法内联的注意点
- 尽量让方法体小一些,在方法体中不要编写大量的代码
- 尽量使用 final 、private、static 关键字修饰方法,避免因为多态,需要对方法做额外的检查。(还有可能在检查之后,发现不能确定方法唯一)
- 一些场景下,可通过Jvm参数修改阈值,从而让更多方法内联。
内联可能带来的问题
优化无非就是时间换空间,空间换时间。内联本质上是空间换时间。经过内联后方法的代码里会变多,在一些极端环境下,会造成
- CodeCache 的溢出,导致JVm退化成解释执行模式。CodeCache 是热点代码测缓存区,即使编译器编译过的代码、以及本地代码,都会存放在CodeCache区。 JDK8里CodeCache默认是240M。
内联相关参数
测试方法内联性能
循环执行方法查看性能
public class InlineTest {
public static void main(String[] args) {
Random random = new Random();
long start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
int a = random.nextInt();
int b = random.nextInt();
int c = random.nextInt();
int d = random.nextInt();
add1(a,b,c,d);
}
long end = System.currentTimeMillis();
System.out.println("执行花费了 " + (end - start)+"ms" );
}
private static int add1(int x1,int x2,int x3,int x4){
return add2(x1, x2) + add2(x3, x4);
}
private static int add2(int x1, int x2) {
return x1 + x2;
}
}
加入Jvm参数
-XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining
执行方法查看控制台打印信息
可以看到打印出了方法大小
add1(12 bytes) 方法体的字节是12字节 ; add2 (4 bytes)add2方法体大小是4字节
inline 表示方法已经内联
(hot) 表示是热点方法
修改JVM参数阈值,使其不满足内联条件
-XX:FreqInlineSize=1 内联条件1字节
-XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining -XX:FreqInlineSize=1
控制台打印如下:可以看到方法体大小超过了阈值,方法就没有内联。
正常情况下,编译器这块使用默认值就可以了,不需要区优化改动。因为现在Jvm已经非常智能,l非常强大了。一般来说可以忽略掉(编译器优化)这些细节,优化的工作交给JvM 来完成。
但是当项目出现性能瓶颈的时候,要能想到可以做方法内联的优化。