标题不能完全表达我的意思,但基本上我的意思是,在一个类Base
中给定一些方法m()
,如果在某个子类Derived
中没有重写该方法,则当前JVM中的JIT编译器是否能够在有意义的情况下仍然"专门化"0 m()
,或者继承并且不重写Base.m()
的派生类是否共享相同的编译代码?
这种专门化在派生类定义了使m()
变得更简单的东西时是有意义的。例如,为了讨论而言,假设m()
调用另一个成员函数n()
,在派生类中,n()
被定义为当n()
内联到m()
中时,后者大大简化。
具体来说,考虑以下类中的两个非抽象方法(它们都是m()
类型的方法,而抽象方法是相应的n()
方法):
public class Base {
abstract int divisor();
abstract boolean isSomethingEnabled();
int divide(int p) {
return p / divisor();
}
Object doSomething() {
if (isSomethingEnabled()) {
return slowFunction();
} else {
return null;
}
}
两种方法都依赖于抽象方法。假设您现在有一个像这样的Derived
:
public class Derived extends Base {
final int divisor() {
return 2;
}
final boolean isSomethingEnabled() {
return false;
}
}
现在,
divide()
和doSomething()
方法的有效行为非常简单,divide
不是对任意数进行完整的除法,而只是可以通过位运算简单地将其减半。 doSomething()
方法始终返回false。我假设当JIT
编译divide()
或doSomething()
时,如果Derived
是唯一的子类,则一切顺利:目前存在(当前)仅有的两个抽象调用的一个可能实现,并且CHA将启动并内联唯一可能的实现,一切都很好。然而,在其他派生类存在的更一般情况下,我不确定JVM是否只使用
invokevirtual
调用编译Base
中方法的一个2版本,还是聪明到说:“嘿,即使Derived
没有覆盖divisor()
,我也应该专门为它编译一个版本,因为它会更简单”。当然,即使没有专门的重新编译,积极的内联通常也能使其正常工作(即,在已知或甚至只是可能是
Derived
的类上调用divide()
时,内联可能会为您提供良好的实现,但是同样,有很多情况下这种内联不会被执行。
0 我所说的专门化并没有特定意义,除了在某些受限领域中编译另一个适当的函数版本之外,就像内联是针对特定调用点的专门化形式一样,或者就像大多数函数都针对当前上下文进行了某种程度的专门化(例如,加载的类,关于nullness的假设等)。 1 特别是,当人们说“JVM能否blah blah”时,通常是指Hotspot,并且我也主要在Hotspot中,但也包括任何其他JVM是否也可以做到这一点。 2 好吧,当然,您可能会有几个函数版本,用于栈替换,不同的编译器级别,出现去优化时等等...
PrintCompilation
输出中猜到了这一点。你可以通过将m()
方法复制粘贴到Derived
中(如果它在Base
中不是final
)来部分解决它,但这违反了DRY原则。我进行了小的编辑以澄清该方法最多只有一个“当前”版本,因为“实际”的说法有些过于强烈:先前的方法版本仍然可能同时运行等等 - 因此它们在其代码可能无限期地执行的意义上是非常真实的(至少对于长时间运行的方法而言)。如果您不同意措辞,请随时恢复原样 :) - BeeOnRope