⌈JVM⌋方法内联优化

内联优化

在内部编译方法的时候,JVM根据内部逻辑分析是否热点代码,如果代码足够热,JVM会对方法内联优化,节省额外的方法调用开销(方法栈帧的生成、参数字段的压入、栈帧的弹出、指令执行地址跳转)。

方法内联是由JIT编译器在运行时完成的。既然涉及到编译,方法内联也是有一定的开销的,包括cpu时间和内存,所以这又是一个trade-off的老问题了。JIT根据以下信息决定是否进行内联:

  • 被调用方法是否足够hot。这个取决于该方法被调用的次数,次数阈值默认值为10,000。即运行时被调用次数超过10,000的方法,可以被认为是hot。
  • 被调用方法大小是否合适。对于过大的方法,JIT认为它是不适合做内联的。这个方法大小阈值由-XX:FreqInlineSize指定,不建议修改。即大于这个阈值size的方法,不考虑进行内联
  • 被调用方法运行时其实现是否可以唯一确定。显然,对于类方法、私有方法和final方法,JIT是可以唯一确定它们的具体实现代码的(这里对应字节码中的invokestatic和invokespecial);另一方面,对于public方法调用,它所指向的具体实现可能是自身、父类、子类的方法实现代码(多态),只有当JIT能唯一确定方法的具体实现时,才有可能完成内联(对应字节码中的invokevirtual和invokeinterface)

另外内联优化不仅仅消除了调用开销,被内联优化的方法会编译成native code(机器码)放在JVM Code Cache nmethod中,使多次调用方法时性能提升。

函数调用的代码:

package RUN.java;

class Solution1 {
    //函数调用: 0.2982 ms
    private int gogogo(int n) {
        for (int i = 0; i < 10; i++) {
            n <<= 1;
        }
        return n;
    }

    public static void main(String[] args) {
        long start = System.nanoTime();
        Solution1 s = new Solution1();
        int n = 1;
        int ans = 0;
        for (int i = 0; i < 10000; i++) {
            ans += s.gogogo(n);
        }
        long end = System.nanoTime();
        System.out.println("函数调用: " + (end - start) / 1000000.0 + " ms");
    }
}

直接内联的代码:

package RUN.java;

class Solution {
    //内联调用: 1.003 ms
    public static void main(String[] args) {
        long start = System.nanoTime();
        int n = 1;
        int ans = 0;
        for (int i = 0; i < 10000; i++) {
            for (int i1 = 0; i1 < 10; i1++) {
                n <<= 1;
            }
            ans += n;
        }
        long end = System.nanoTime();
        System.out.println("内联调用: " + (end - start) / 1000000.0 + " ms");
    }
}
  • 执行前增加 -XX:+PrintCompilation 参数,输出编译结果。具体每项内联信息所代表的意思