为什么在JVM兼得`invokespecial`和`invokestatic`操作码?(Why do

2019-08-04 08:42发布

这两个指令都使用静态而非动态调度。 这似乎是唯一的实质性区别在于invokespecial永远有,作为第一个参数,一个对象,它是类的一个实例是,派出方法属于。 然而, invokespecial实际上并不把对象那里。 编译器是一个负责使通过发射之前发射的堆栈操作的适当序列发生invokespecial 。 所以更换invokespecialinvokestatic应该不会影响运行栈/堆被操纵的方式-尽管我期望它会造成VerifyError违反规范。

我很好奇背后做的是基本上做同样的事情,两个不同的指令可能的原因。 我看了看在OpenJDK解释的来源,它似乎像invokespecialinvokestatic几乎相同的处理。 是否有两个单独的指令帮助JIT编译器更好地优化代码,或者它帮助类文件验证器更有效地证明了一些安全性? 或者这只是在JVM的设计怪癖?

Answer 1:

有定义:

  • http://docs.oracle.com/javase/specs/jvms/se5.0/html/Instructions2.doc6.html#invokestatic
  • http://docs.oracle.com/javase/specs/jvms/se5.0/html/Instructions2.doc6.html#invokespecial

有显著差异。 说,我们要设计一个invokesmart指令,这之间巧妙地选择inkovestaticinvokespecial

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

首先,你会invokesmart foo/Bar.baz(I)I的意思吗? 这可能意味着:

  • 静态方法调用foo.Bar.baz消耗int从操作数堆栈,并增加了一个int// (int) -> (int)
  • 实例的方法调用foo.Bar.baz消耗foo.Barint从操作数堆栈,并增加了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%肯定。

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



Answer 2:

免责声明:这是很难说肯定的,因为我从来没有读过关于这个明确的Oracle的声明,但我几乎认为这是什么原因:

当你在Java字节码,你可以询问其他指令同样的问题。 为什么要验证阻止你推二:当int堆栈上的S和对待他们作为一个单一的long之后? (试试吧,它会阻止你。)你可以争辩说,允许这一点,你可以用更小的指令集表达了同样的逻辑。 (要使用这种说法走得更远,一个字节无法表达太多的指示,Java字节代码集,因此应尽可能降低。)

当然,从理论上讲,你不需要为推动字节码指令int S和long s到堆栈,你是对的事实,你会不会需要两个指令INVOKESPECIALINVOKESTATIC为了表达方法调用。 一种方法,通过它的方法描述符 (名称和原始参数类型)来标识,你不能同时定义的静态和相同类内的相同描述的非静态方法。 而为了验证字节代码,Java编译器必须检查目标方法是否是static还是。

备注 :此矛盾v6ak的答案。 然而,非静态方法的方法描述符是不改变,以包括到参考this.getClass() 因此,Java运行时总是可以推断适当的方法从方法描述为一个虚构的结合INVOKESMART指令。 见JVMS§4.3.3。

这么多的理论。 然而,由这两种类型的调用表达的意图有很大的不同。 请记住,Java字节代码应该比javac的其他工具可以用来创建JVM的应用,以及。 随着字节码,这些工具产生的东西,比你的Java源代码更类似于机器代码。 但它仍然是相当高的水平。 例如,字节代码仍然被验证,并且当编译成机器代码的字节代码被自动优化。 但是,字节码是故意包含为了使字节代码的含义更加明确一些冗余的抽象。 而就像Java语言使用了类似的事情不同的名称,以使语言更具可读性,字节码指令集包含一定的冗余度为好。 而作为另一个好处,验证以及字节码解释/编译就可以来一个方法的调用类型并不总是需要被推断,但在字节码是明确指出加快。 因为验证,解释和汇编,在运行时完成这是可取的。

作为最后一个故事,我应该指出,一个类的静态初始化<clinit>未标记static的Java 5之前在此背景下,静态调用也可以通过该方法的名字推断,但这会造成更大的运行时开销。



Answer 3:

为了得到关于这些代码的一个明确的实际想法,你需要添加的Eclipse插件ASM在Eclipse IDE中,并找出为其创建了您的样本Hello World程序生成的字节码。



文章来源: Why does the JVM have both `invokespecial` and `invokestatic` opcodes?
标签: jvm bytecode