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

12

这两个指令使用静态而非动态分派。看起来唯一的实质性差别在于,invokespecial 的第一个参数总是一个对象,该对象是分派方法所属类的实例。然而,invokespecial 实际上并没有将对象放在那里,编译器通过在发出 invokespecial 之前发出适当的堆栈操作序列来使其发生。因此,用 invokestatic 替换 invokespecial 不应影响运行时堆栈 / 堆的操作方式--尽管我预计它会违反规范导致 VerifyError

我很好奇为什么要制作两个执行本质相同的指令。我查看了 OpenJDK 解释器的源代码,似乎 invokespecialinvokestatic 的处理方式几乎一样。分开两个指令是否有助于 JIT 编译器更好地优化代码,或者是否有助于类文件验证器更有效地证明某些安全属性?还是这只是 JVM 设计中的怪癖?


2
我的怀疑是这有助于使验证器更加高效(不必检查实例是否对静态方法正确设置)。 - Thilo
2个回答

3
免责声明:虽然我从未看过Oracle的明确声明,但我基本上认为这是原因:
当您查看Java字节码时,您可能会对其他指令提出同样的问题。为什么在将两个int推送到堆栈并立即将它们视为单个long时,验证器会阻止您?(尝试一下,它会阻止您。)您可以争辩说,通过允许这样做,您可以使用更小的指令集来表达相同的逻辑。(进一步深入这个论点,一个字节不能表达太多指令,因此Java字节码集应该尽可能缩减。)
当然,在理论上,您不需要一个字节码指令来将int和long推送到堆栈上,您关于不需要 INVOKESPECIAL 和 INVOKESTATIC 两个指令以便表达方法调用的事实也是正确的。方法通过其方法描述符(名称和原始参数类型)唯一标识,您无法在同一类中定义具有相同描述的静态和非静态方法。为了验证字节码,Java编译器必须检查目标方法是否为静态。
备注:这与v6ak的答案相矛盾。但是,非静态方法的方法描述符不会更改以包括对this.getClass()的引用。因此,Java运行时始终可以从方法描述符中推断出适当的方法绑定,用于假设的 INVOKESMART 指令。请参见JVMS§4.3.3。
这就是理论。但是,两种调用类型表达的意图相当不同。请记住,Java字节码应该被其他工具(而不仅仅是javac)用于创建JVM应用程序。使用字节码,这些工具会生成与机器代码更相似的东西。但是它仍然相当高级。例如,字节码仍然经过验证,并且在编译为机器代码时,字节码会自动进行优化。但是,字节码是一种有意包含一些冗余的抽象,以使字节码的含义更加明确。就像Java语言使用不同的名称来表示类似的事物以使语言更易读一样,字节码指令集也包含一些冗余。作为另一个好处,验证和字节码解释/编译可以加速,因为方法的调用类型不必总是被推断,而是在字节码中明确说明。这是可取的,因为验证、解释和编译是在运行时完成的。
最后,我应该提到,在Java 5之前,类的静态初始化程序未标记为静态。在这种情况下,方法的静态调用也可以通过方法的名称推断出来,但这会导致更多的运行时开销。

我同意我错过了一个事实,即不能对静态方法和非静态方法使用相同的方法签名。我已经尝试过了,它会生成java.lang.ClassFormatError:类文件Foo中存在重复的方法名称和签名。我会进行更正。 - v6ak

2
以下是定义:
这两者有重大的区别。假设我们想设计一个“智能调用(invokesmart)”指令,它会在“inkovestatic”和“invokespecial”之间进行智能选择。
首先,区分静态调用和虚拟调用不成问题,因为即使其中一个是静态的,而另一个是虚拟的,我们也不能同时拥有相同名称、相同参数类型和相同返回类型的两个方法。JVM不允许这种情况发生(出于奇怪的原因)。感谢raphw注意到了这一点。 首先,invokesmart foo/Bar.baz(I)I意味着什么?它可能表示以下内容:
  • 一个静态方法调用foo.Bar.baz,从操作数栈中获取int并添加另一个int。 // (int) -> (int)
  • 一个实例方法调用foo.Bar.baz,从操作数栈中获取foo.Bar和int,然后添加int。 // (foo.Bar, int) -> (int)
我们该如何从中进行选择?这两种方法都可能存在。 我们可以尝试通过要求静态调用使用foo/Bar.baz(Lfoo/Bar;I)来解决它。但是,我们可能会同时拥有public static int baz(Bar, int)和public int baz(int)。
我们可以说这并不重要,可能禁用这种情况(我认为这不是一个好主意,但只是想象一下)。那会意味着什么?
  • 如果该方法是静态的,则可能没有其他限制。另一方面,如果该方法不是静态的,则存在一些限制:“最后,如果已解析的方法受到保护(§4.6)并且它是当前类的成员或当前类的超类的成员,则objectref类必须是当前类或当前类的子类。”
  • 还有一些进一步的差异,请参见有关ACC_SUPER的注释。
  • 这意味着在字节码验证之前必须加载所有引用的类。我希望现在不需要这样做,但我不能100%确定。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接