有什么理由不将 invokevirtual 和 invokeinteface 字节码指令合二为一吗?

Is there any reason for not invokevirtual and invokeinteface bytecode instruction into one?

是否有任何理由将调用非静态非构造函数方法的指令分为两条不同的指令而不是一条统一指令,如invokeinstance?它与一些随机的内部 JVM 机制有什么关系还是另一个可怕的遗留问题?

我知道我们有 invokespecial 因为调用构造函数需要名称检查,标记另一个构造函数已被执行等,invokestatic 因为我们不需要 objectref 转储到新的堆栈帧中。然而,对于 Sun 选择将可能的通用指令分散到 invokevirtualinvokeinterface 中,并没有一个容易找到的原因。如果不拆分它,ASM 代码可能会简单得多,因为我们不必查看所有超接口来查看这是否是一个接口方法,从而增加代码的复杂性。

Invokeinterface 不同,因为接口仅在运行时进行类型检查。使用虚拟方法,您可以静态确定该类型是定义该方法的 class 的子类型。对于一个接口,如果不知道该值的运行时类型,就不可能确定该值是否具有实现该接口的类型。

考虑以下伪代码(请注意,这在 Java 中是不允许的,但 JVM 允许等效的字节码)

class A
class B extends A implements Foo

A a = new B()
a.fooMethod()

无法静态知道 a 是否实现了 Foo,因为静态类型 A 不实现 Foo 但实际运行时类型 B 实现了。

编辑:上述示例将被 Java 编译器拒绝,但 JVM 不会。您可能想知道为什么 JVM 不只是应用与编译器相同的规则。不同之处在于 JVM 没有关于局部变量的源代码级类型信息。考虑以下示例,它在 Java.

中是允许的
class A
class B extends A implements Foo
class C extends A implements Foo

Foo x = null;
if (whatever) {
x = new B();
} else {
x = new C();
}
x.fooMethod();

JVM 不知道 x 的预期类型(没有堆栈映射表,直到很久以后才引入),因此它推断 x 的类型为 A,这并没有实现Foo。因此,如果它试图静态检查接口作为验证时间,它会拒绝有效的 Java 代码!唯一可行的解​​决方案是不对接口进行类型检查。

为了安全地检查接口,JVM 必须能够推断出像 "subclass of A which also implements Foo" 这样的类型,这显然给必须快速高效的事情增加了巨大的复杂性。所以设计师没有走这条路是有道理的。

P.S。 Invokespecial 不仅用于构造函数——它还用于私有和超级方法调用。很可能它最初是作为优化的单独指令,因为调用的方法在加载时是已知的,而不是随着目标的运行时类型而变化。其实最初叫invokenonvirtual.