JVM-方法内联

JVM-方法内联

jvm学习记录

方法内联(inlining,对性能的提升很大):方法内联可以减少方法调用,从而减少方法栈的创建。
简单点说:就是把被调用方函数代码"复制"到调用方函数中,减少因函数调用开销的技术。
相信大家都知道循环的速度比递归快很多,就是这个原因,另外方法内联后,还使得一些JIT更深入的优化变成可能。jvm可以通过两个启动参数来控制字节码大小为多少的方法可以被内联:
-XX:MaxInlineSize: 能被内联的方法的最大字节码大小,默认值为35Byte,这种方法不需要频繁的调用。比如:一般pojo类中的getter和setter方法,它们不是那种调用频率特别高的方法,但是它们的字节码大小非常短,这种方法会在执行后被内联。
-XX:FreqInlineSize: 调用很频繁的方法能被内联的最大字节码大小,这个大小可以比MaxInlineSize大,默认值为325Byte(和平台有关,我的机器是64位windows)。

函数的调用过程

  1. 首先会有一个执行栈,存储它们的局部变量、动态链接、方法出口等。
  2. 当一个方法被调用,一个新的栈帧会被加载到栈顶,分配的本地变量和参数会存储在这个栈帧中。
  3. 跳转到目标方法代码执行
  4. 方法返回的时候,弹栈并销毁
  5. 返回原来的地址执行

注:这就是通常说的压栈和出栈过程,因此,函数调用需要有一定的时间开销和空间开销,当一个方法体不大,但又频繁被调用时,这个时间和空间开销会相对变得很大,变得非常不划算,同时降低了程序的性能。根据二八原则,80%的性能消耗其实是发生在20%的代码上,对热点代码的针对性优化可以提升整体系统的性能

方法内联的条件
JVM会自动识别热点方法,并对它们使用方法内联进行优化,那优化的条件是什么呢?

通常这个值由-XX:CompileThreshold参数进行设置:

使用client编译器时,默认为1500;
使用server编译器时,默认为10000;

当某个方法被连续调用这么多次后,就相当于是一个热点方法,就可能被JVM进行方法内联优化,注意是可能。

为什么呢?
其中有个比较常见的原因就是这个方法体太大了,分为两种情况:

  1. 如果方法是经常执行的,默认情况下,方法大小小于325字节的都会进行内联(可以通过 -XX:MaxFreqInlineSize=N 来设置这个大小)
  2. 如果方法不是经常执行的,默认情况下,方法大小小于35字节才会进行内联(可以通过 -XX:MaxInlineSize=N 来设置这个大小)

效果演示
来一个简单的测试代码

public class ListTest {
    
    
    public static void main(String[] args) {
    
    
        ListTest listTest = new ListTest();
        for (int i = 0; i < 9999; i++) {
    
    
            listTest.printStr();
        }
    }
    public int printStr(){
    
    
        return p(1,3)+p(2,4);
    }
    public int p(int i,int s){
    
    
        return s+i;
    }
}

添加三个JVM参数:(-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining)

  • PrintCompilation,打印JIT编译日志
  • UnlockDiagnosticVMOptions和PrintInlining,打印inlining信息

接下来分别演示一下没有出现方法内联优化与出现优化的情况,看控制台打印出的日志信息
在这里插入图片描述
在这里插入图片描述

注:inline (hot)表示当前调用已被内联
实验测试过程中,也在5000以后有时候也出现了方法内联优化,而5000以内基本不会出现,虽然p()方法被调用了10000+,但是也不一定会出现内联优化。


方法内联可以消除调用本身带来的开销,还可以进一步出发更多的优化。
内联越多,生成代码的执行效率越高。 然而对于即时编译器来说,内联越多,编译时间也就越长,而程序达到峰值的时刻也将被推迟。也会导致生成的机器码越长。

内联规则:

  • 由@Forcelnline 注解的方法(仅限于 JDK 内部方法)会被强制内联。
  • 调用字节码对应的符号引用未被解析、目标方法所在的类未被初始化,或者目标方法是 native 方法,都将导致方法调用无法内联。
  • C2 不支持内联超过9层的调用,以及1层的直接递归调用。
  • 总体来说,即时编译器中的内联算法,更青睐于小方法。

虽然JIT号称可以针对代码全局的运行情况而优化,但是JIT对一个方法内联之后,还可能因为方法被继承,导致需要类型检查而没有达到性能的效果。
想要对热点的方法使用上内联的优化方法,最好尽量使用final、private、static这些修饰符修饰方法,避免方法因为继承,导致需要额外的类型检查,而出现效果不好的情况。

结论

  1. 针对热点方法,想要通过JIT内联优化来提升性能的建议
  2. 更小的方法体,JVM总是偏好更小的方法。
  3. 尽量使用final、private、static修饰符
  4. 使用+PrintInlining参数校验效果(这两个要一起使用 【-XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining】)

猜你喜欢

转载自blog.csdn.net/qq_41257365/article/details/107588799
今日推荐