大透编译器,聊聊方法内联

方法内联

上篇文章中,和大家聊过JVM会用编译的方式为代码提速,那在这个基础上,即时编译器还做了很多的优化措施,从而进一步的提升性能,我们这次聊的方法内联就是即时编译器优化措施之一。

来看下这段代码:

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;
}

这段代码很简单,add1里面调用了两次add2方法,那么大家想一想,编译器怎样优化这段代码呢?

我们都知道,调用方法需要经过压栈出栈的操作,压栈出栈都是有开销的,比如说,压栈的话会向栈里存数据,所以存在内存的开销,同时呢,压栈和出栈都是需要消耗时间的,所以同时还有时间的开销,那么如果这段代码调用次数不多,那么压栈和出栈的开销倒也无所谓,但是假设,这段代码调用的非常频繁,比方说,每秒要调用两万次,那么累计下来的开销还是比较可观的,那么有什么办法去优化呢?
当然是有的,JVM会自动识别热点方法,并自动的进行方法的内联

所谓方法内联,就是把目标方法复制到发起调用的方法之中,避免发生真实的方法调用

比如上面那段代码,进行内联后就会变成这个样子:

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 add1(int x1, int x2, int x3, int x4) {
	return x1 + x2 + x3 + x4;
}

也就是说,他会自动的把add1方法,和add2方法合并到一起去,从而减少压栈和出栈的操作。

那么需要注意的是,使用方法内联是有条件的:

  • 方法体要足够小,如果方法体太大,JVM是不会内联的
    • 热点方法: 如果方法体小于325字节会尝试内联,可用-XX:FreqInlineSize修改大小
    • 非热点方法:如果方法体小于35字节,会尝试内联,可用-XX:MaxInlineSize修改大小
  • 被调用方法运行时的实现可以被唯一确定
    • static方法、private 方法及final方法,JIT可以唯一确定具体的实现代码。
    • pubic的实例方法,指向的实现可能是自身、父类、子类的代码,当且仅当JIT能够唯一确定方法的具体实现时,才有可能完成内联。

那么我们根据这两点条件,可以总结出使用方法内联所注意的点:

  1. 写代码的时候,尽量让方法体小一些,避免在一个方法里面编写大量的代码。
  2. 尽量使用final、private、static关键字修饰方法,避免因为多态,需要对方法做额外的检查。
  3. 在某些场景下,可通过JVM参数修改阈值,从而让更多的方法内联。

下面我们探讨一下,使用方法内联可能带来的问题:
方法内联不是万能的,他本是也有缺点,方法内联本事就是一种用空间换时间的玩法,用即时编译器在编译期间,把方法调用连接起来,从而减少进栈和出栈的开销,但是呢,经过内联之后的代码会变多,而增加的代码量又取决于方法的调用次数,以及方法本身的大小,所以在一些极端的场景下,内联会出现很多问题:
比如:CodeCache 溢出,导致JVM退化成解释执行模式

内联相关JVM参数

参数名 默认 说明
-XX:+PrintInlining - 打印内联详情,该参数需要和-XX:+UnlockDiagnosticVMOptions,且放在该参数之后
-XX:+UnlockDiagnosticVMOptions - 打印JVM诊断相关的信息
-XX:MaxInlineSize=n 35 如果非热点方法的字节码超过该值,则无法内联,单位字节
-XX:FreqInlineSize=n 325 如果热点方法的字节码超过该值,则无法内联,单位字节
-XXInlineSmallCode=n 1000 目标编译后生成的机器码代销大于该值,则无法内联,单位字节
-XX:MaxInlineLevel=n 9 内联方法的最大调用帧数(嵌套调用的最大内联深度)
-XX:MaxTrivialSize=n 6 如果方法的字节码小于该值,则直接内联,单位字节
-XX:MinInliningThreshold=n 250 如果目标方法的调用次数低于该值,则不去内联
-XX:LiveNodeCountInliningCutoff=n 40000 编译过程中最大活动节点数(IR节点)的上限,仅对C2编译器有效
-XX:InlineFrequencyCount=n 100 如果方法的调用点(call site)的执行次数超过该值,则触发内联
-XX:MaxRecursiveInlineLevel=n 1 递归调用大于那么多次就不内联
-XX:+InlineSynchronizedMethods 开启 是否允许内联同步方法
上一篇:联合


下一篇:C++题解 差分矩阵