JVM 的内联和继承的交互

Interaction of inlining and inheritance for the JVM

JIT 是否会在抽象 class 的继承方法中内联调用,该方法在所有子 class 中都是超态的,但对于给定的子 class 是单态的?

假设我们有一个接口 IFace,具有从 AZ 的多种实现。还假设我们有一个抽象 class Foo,它包含一个 IFace 类型的字段,并在其唯一方法(即 final)中调用该字段上的一个方法,并且我们有许多 Foo 的子 classess,所有这些都什么都不做,像这样(为简洁起见省略了构造函数):

interface IFace {
   void act();
}

final class A extends IFace {
    ...
}

...

final class Z extends IFace {
    ...
}

abstract class Foo {
    final IFace field;

    public final void doAThing() {
        field.act();
    }
}

final class FooA extends Foo {
}

 ...

final class FooZ extends Foo {
}

假设 'by accident' FooX 的所有实例实际上都有 field 个对应类型 X 的值。进一步假设我们在一个非常热点中有这样的代码:

for (final Foo foo : foos) {
    foo.doAThing();
}

其中 foos 属于 List<Foo> 类型,很大,并且包含 Foo 的所有子 class 的随机且大致均匀分布。

foo.doAThing() 调用站点非常庞大,因此不会发生内联。但是,在确定 field.act() 是否可以内联到 doAThing 时,类型分析是针对每个子 class 还是仅针对整个 Foo 进行的?如果每个子 class,我们将有 26 个版本的 doAThing,每个版本对 field.act() 的调用都是单态和可内联的,但如果我们只有一个,则对 [=26] 的调用=] 将是超态的而不是可内联的。

(我知道这个问题的答案可能是 JDK 特定的,所以我特别询问最近版本的 OpenJDK)

配置文件数据按方法收集。鉴于 doAThing() 在您的情况下是最终的,因此所有子类只有一种方法。因此 field.act() 的配置文件可能会被其他 Foos 破坏,如果 doAThing() 实际上被调用了很多次。

目前(从 JDK8u60 开始)类型配置文件不考虑上下文。
有一个错误报告 JDK-8015416 可以在未来的某个时间修复此问题。

概要分析是分析程序而非代码的运行时行为 的行为。因此,它不是“每个子 class 完成”,也不是“对 Foo 整体”完成,因为两者都没有意义。

想象一下,JVM 会定期查看活动线程的调用堆栈,以查看它们实际执行的具体方法以及调用它们的具体方法。堆栈检查甚至可能会增加一些堆栈帧,但通常会限制在一定数量的帧以降低检查成本。

这个分析已经提供了两个必要的信息,是否有显性调用目标,如果不是单态行为,它是哪个方法。因此,存在多少替代实现并不重要,只要它们很少被调用即可。

根据此信息进行优化还意味着 JVM 会注意检测程序的行为是否发生变化,并且优化后的代码必须取消优化,如果没有被不同优化的变体替换的话。

JVM 知道哪些 classes 曾经被实例化(通常甚至不会在首次使用前加载 classes)也很有帮助。因此,如果在运行时对于 interface 只有一个实现 class,则将假定单态行为,而无需等待探查器来证明它。由于 JVM 知道何时加载新的 subclass/implementation class,因此它可以及时取消优化。