编译器优化:方法内联

方法内联的思想是,把目标方法的代码复制代发起调用的方法之中,避免发生真实的方法调用。

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

如上代码,我们知道线程执行方法时,会向虚拟机栈压入栈帧,add1方法中调用了两次add2方法会压入两次add2的栈帧。频繁出入栈操作,消耗内存和时间。

JVM可以对上面的操作进行方法内联优化,优化为下面代码。

private static int add1(int x1, int x2, int x3, int x4) {
    return x1 + x2 + x3 + x4;
}

方法内联的条件有两个:

  1. 方法体足够小。

    1. 热点方法,如果方法体小于325字节会尝试内联,可以使用 -XX:FreqInlineSize修改大小。
    2. 非热点方法,如果方法体小于35字节尝试内联, -XX:MaxInlineSize
  2. 被调用的方法在运行时的实现可以被唯一确认。

    1. static、private、final方法,JIT可以唯一确认具体的实现代码。
    2. public实例方法,指向的实现可能是自身、父类、子类的代码(多态),只有当JIT唯一确认方法实现时,才有可能内联。

内联可能带来的问题:会导致方法变大,使得CodeCache溢出,导致JVM退化成解释执行模式。

一般情况,使用默认JVM参数就好。

测试方法内联

@Slf4j
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 long compute() {
        long start = System.currentTimeMillis();
        int result = 0;
        Random random = new Random();
        for (int i = 0; i < 10000000; i++) {
            result = add1(random.nextInt(), random.nextInt(), random.nextInt(), random.nextInt());
        }
        long end = System.currentTimeMillis();
        return end - start;
    }

    public static void main(String[] args) {
        long compute = compute();
        log.info("花费{}ms", compute);
    }
}

花费362ms
花费483ms

设置JVM参数,打印内联日志,开关内联(通过设置内联阈值)。

-XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining -XX:FreqInlineSize=1

默认开启方法内联,比直接关掉运行更快。

JVM参数备注

image

posted @ 2021-12-26 17:17  Awecoder  阅读(369)  评论(1编辑  收藏  举报