JVM笔记 -方法内联

在编译过程中遇到方法调用时,就是将目标方法的方法体 / 代码块纳入编译范围之中,并取代原方法调用的优化手段,是JVM 最重要的编译器优化,没有之一

optimize before

static class A{
    int value;
    final int get(){
        return value;
    }
}

public void foo(){
    y = b.get();
    // do stuff..
    z = b.get();
    sum = y + z;
}

optimize after

static class A{
    int value;
    final int get(){
        return value;
    }
}

public void foo(){
    y = b.value;
    // do stuff..
    z = b.value;
    sum = y + z;
}

可以看到代码中函数的调用转换成了变量访问,去除了方法调用的成本

方法调用:查找函数的位置 -> 压入栈帧 -> 访问变量 -> 恢复执行

类型继承分析 / Class Hierarchy Analysis(CHA)

解决 虚方法 无法轻易在静态编译阶段寻找到到具体的调用函数

通过整个应用程序范围查的类型分析技术,实现判断目前已经加载类中的方法,某个接口是否包含多种实现;某个子类是否覆盖父类的函数;

激进优化 / 完全去虚化

如果不是虚方法,那么可以直接内联就好了,不用有任何担心,它绝对是安全的。如果是虚方法那么向 CHA 查询有多少个实现,如果只有一个,那么可以直接内联该实现函数,我们称之为守护内联(Guarded Inlining)

条件去虚化
退路 / 去优化

当 CHA 发现虚方法只有一种实现时,直接关联实现函数,是过于激进的。我们需要解决 JAVA 程序在运行时,加载了新类型时,虚方法有多种实现的问题

退路就是,假设程序加载了导致继承关系发生变化的新类,那么就必须抛弃已经变异的守护内联,退回到解释执行,或者重新进行即时编译。

方法内联的条件

方法内联能够触发更多的优化。通常而言,内联越多,生成代码的执行效率越高。然而,对于即时编译器来说,内联越多,编译时间也就越长,而程序达到峰值性能的时刻也将被推迟。

此外,内联越多也将导致生成的机器码越长。在 Java 虚拟机里,编译生成的机器码会被部署到 Code Cache 之中。这个 Code Cache 是有大小限制的(由 Java 虚拟机参数 -XX:ReservedCodeCacheSize 控制)。

因此,即时编译器不会无限制地进行方法内联。下面我便列举即时编译器的部分内联规则。(其他的特殊规则,如自动拆箱总会被内联、Throwable 类的方法不能被其他类中的方法所内联,你可以直接参考JDK 的源代码。)

  • -XX:CompileCommand 中的 inline 指令指定的方法,以及由 @ForceInline 注解的方法(仅限于 JDK 内部方法),会被强制内联

  • -XX:CompileCommand 中的 dontinline 指令或 exclude 指令(表示不编译)指定的方法,以及由 @DontInline 注解的方法(仅限于 JDK 内部方法),始终不会被内联

  • 如果调用字节码对应的符号引用未被解析、目标方法所在的类未被初始化,或者目标方法是 native 方法,都将导致方法调用无法内联。

  • C2 不支持内联超过 9 层的调用(可以通过虚拟机参数 -XX:MaxInlineLevel 调整),以及 1 层的直接递归调用(可以通过虚拟机参数 -XX:MaxRecursiveInlineLevel 调整)。

如果方法 a 调用了方法 b,而方法 b 调用了方法 c,那么我们称 b 为 a 的 1 层调用,而 c 为 a 的 2 层调用。

  • 即时编译器将根据方法调用指令所在的程序路径的热度,目标方法的调用次数及大小,以及当前 IR 图的大小来决定方法调用能否被内联。

参考

IBM:深入浅出 JIT 编译器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值