为什么JVM同时具有`invokespecial`和`invokestatic`操作码?

int*_*nt3 10 jvm bytecode

这两个指令都使用静态而非动态分派.似乎唯一重要的区别在于invokespecial,作为第一个参数,它总是具有一个对象,该对象是调度方法所属的类的实例.但是,invokespecial实际上并没有将物体放在那里; 编译器负责通过在发射之前发出适当的堆栈操作序列来实现这一点invokespecial.因此,更换invokespecialinvokestatic不应该影响运行栈/堆被操纵的方式-虽然我预计这将导致VerifyError违反规范.

我很好奇制作两个截然不同的指令背后可能的原因.我看了一下OpenJDK解释器的来源,它似乎invokespecialinvokestatic处理几乎相同.有两个单独的指令有助于JIT编译器更好地优化代码,还是帮助类文件验证器更有效地证明某些安全属性?或者这只是JVM设计中的一个怪癖?

v6a*_*6ak 3

有以下定义:

\n\n\n\n
\n\n

存在显着差异。假设我们要设计一条指令,它将在和 之间invokesmart明智地进行选择:inkovestaticinvokespecial

\n\n

首先,区分静态和虚拟调用不会有问题,因为我们不能有两个具有相同名称、相同参数类型和相同返回类型的方法,即使一个是静态的,第二个是虚拟的。JVM 不允许这样做(出于一个奇怪的原因)。感谢 raphw 注意到这一点。

\n\n

首先,invokesmartfoo/Bar.baz(I)I意味着什么?这可能意味着:

\n\n
    \n
  • 静态方法调用foo.Bar.baz,从操作数堆栈中消耗int并添加另一个int.// (int) -> (int)
  • \n
  • 实例方法调用foo.Bar.baz,消耗操作数堆栈中的 和 并foo.Bar添加。intint// (foo.Bar, int) -> (int)
  • \n
\n\n

你会如何选择?两种方法都可能存在。

\n\n

foo/Bar.baz(Lfoo/Bar;I)我们可以尝试通过要求静态调用来解决它。然而,我们可能同时拥有public static int baz(Bar, int)public int baz(int)

\n\n
\n\n

我们可能会说这并不重要,并且可能会禁用这种情况。(我不认为这是一个好主意,只是想象一下。)这意味着什么?

\n\n
    \n
  • 如果该方法是静态的,则可能没有其他限制。另一方面,如果方法不是静态的,则有一些限制:“最后,如果解析的方法是受保护的(\xc2\xa74.6),并且它要么是当前类的成员,要么是当前类的超类,则 objectref 的类必须是当前类或当前类的子类。”
  • \n
  • 还有一些进一步的差异,请参阅有关的注释ACC_SUPER
  • \n
  • 这意味着必须在字节码验证之前加载所有引用的类。我希望现在没有必要,但我不是 100% 确定。
  • \n
\n\n

因此,这意味着非常不一致的行为。

\n