方法内联的思想是,把目标方法的代码复制代发起调用的方法之中,避免发生真实的方法调用。
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;
}
方法内联的条件有两个:
-
方法体足够小。
- 热点方法,如果方法体小于325字节会尝试内联,可以使用
-XX:FreqInlineSize
修改大小。 - 非热点方法,如果方法体小于35字节尝试内联,
-XX:MaxInlineSize
。
- 热点方法,如果方法体小于325字节会尝试内联,可以使用
-
被调用的方法在运行时的实现可以被唯一确认。
- static、private、final方法,JIT可以唯一确认具体的实现代码。
- 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
默认开启方法内联,比直接关掉运行更快。