JVM 的内联和继承的交互
Interaction of inlining and inheritance for the JVM
JIT 是否会在抽象 class 的继承方法中内联调用,该方法在所有子 class 中都是超态的,但对于给定的子 class 是单态的?
假设我们有一个接口 IFace
,具有从 A
到 Z
的多种实现。还假设我们有一个抽象 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,因此它可以及时取消优化。
JIT 是否会在抽象 class 的继承方法中内联调用,该方法在所有子 class 中都是超态的,但对于给定的子 class 是单态的?
假设我们有一个接口 IFace
,具有从 A
到 Z
的多种实现。还假设我们有一个抽象 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,因此它可以及时取消优化。