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

时间:2012-12-20 01:46:25

标签: jvm bytecode

这两个指令都使用静态而非动态分派。似乎唯一重要的区别是invokespecial将始终具有作为其调度方法所属类的实例的对象作为其第一个参数。但是,invokespecial实际上没有把对象放在那里;编译器负责通过在发出invokespecial之前发出适当的堆栈操作序列来实现这一点。因此,将invokespecial替换为invokestatic不应该影响运行时堆栈/堆的操作方式 - 尽管我希望它会导致VerifyError违反规范。

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

3 个答案:

答案 0 :(得分:2)

有定义:


存在显着差异。假设我们想设计一个invokesmart指令,可以在inkovestaticinvokespecial之间巧妙地选择:

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

首先,调用智能foo/Bar.baz(I)I的意思是什么?这可能意味着:

  • 从操作数堆栈中消耗foo.Bar.baz并添加另一个int的静态方法调用int// (int) -> (int)
  • 从操作数堆栈中使用foo.Bar.bazfoo.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节),并且它是当前类的成员或当前类的超类的成员class,那么objectref的类必须是当前类或当前类的子类。“
  • 还有一些差异,请参阅有关ACC_SUPER
  • 的说明
  • 这意味着必须在字节码验证之前加载所有引用的类。我希望现在没有必要,但我不是百分百肯定。

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

答案 1 :(得分:2)

免责声明:很难说清楚,因为我从未读过关于此的明确的Oracle声明,但我认为这就是原因:

当您查看Java字节代码时,您可能会询问有关其他指令的相同问题。为什么验证者在堆栈中推送两个int并在之后将它们视为单个long时会阻止您? (尝试一下,它会阻止你。)你可以说,通过允许这个,你可以用一个较小的指令集表达相同的逻辑。 (为了进一步讨论这个参数,一个字节不能表达太多的指令,因此Java字节代码集应该尽可能地减少。)

当然,从理论上讲,你不需要一个字节代码指令来将intlong推送到堆栈中,你是对的,你不需要两个{ {1}}和INVOKESPECIAL以表示方法调用。方法由其方法描述符(名称和原始参数类型)唯一标识,并且您无法在同一个类中定义具有相同描述的静态和非静态方法。为了验证字节代码,Java编译器必须检查目标方法是否为INVOKESTATIC

备注:这与v6ak的答案相矛盾。但是,非静态方法的方法描述符不会更改为包含对static的引用。因此,Java运行时总是可以从假设的this.getClass()指令的方法描述符推断出适当的方法绑定。见JVMS§4.3.3。

这个理论太多了。但是,两种调用类型表达的意图是完全不同的。请记住,Java字节代码应该被 javac 之外的其他工具用于创建JVM应用程序。使用字节代码,这些工具可以生成与Java源代码更相似的机器代码。但它仍然是相当高的水平。例如,仍然验证字节代码,并在编译为机器代码时自动优化字节代码。但是,字节代码是一种有意包含一些冗余的抽象,以使字节代码的含义更明确。就像Java语言对类似事物使用不同的名称来使语言更具可读性一样,字节码指令集也包含一些冗余。另外一个好处是,验证和字节码解释/编译可以加速,因为方法的调用类型并不总是需要推断,而是在字节代码中明确说明。这是可取的,因为验证,解释和编译是在运行时完成的。

作为最后的轶事,我应该提到类的静态初始值设定项INVOKESMART在Java 5之前没有被标记为<clinit>。在此上下文中,静态调用也可以通过方法的名称来推断,但是这个会导致更多的运行时间开销。

答案 2 :(得分:0)

为了清楚地了解这些代码,您需要在eclipse IDE中添加eclipse plugin for ASM,并找出为您创建的示例Hello World程序生成的字节码。