JVM为什么同时具有`invokespecial`和`invokestatic`操作码? [英] Why does the JVM have both `invokespecial` and `invokestatic` opcodes?

查看:86
本文介绍了JVM为什么同时具有`invokespecial`和`invokestatic`操作码?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Both instructions use static rather than dynamic dispatch. It seems like the only substantial difference is that invokespecial will always have, as its first argument, an object that is an instance of the class that the dispatched method belongs to. However, invokespecial does not actually put the object there; the compiler is the one responsible for making that happen by emitting the appropriate sequence of stack operations before emitting invokespecial. So replacing invokespecial with invokestatic should not affect the way the runtime stack / heap gets manipulated -- though I expect that it will cause a VerifyError for violating the spec.

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

I'm curious about the possible reasons behind making two distinct instructions that do essentially the same thing. I took a look at the source of the OpenJDK interpreter, and it seems like invokespecial and invokestatic are handled almost identically. Does having two separate instructions help the JIT compiler better optimize code, or does it help the classfile verifier prove some safety properties more efficiently? Or is this just a quirk in the JVM's design?

推荐答案

免责声明:很难确定这一点,因为我从未阅读过有关此的明确Oracle声明,但我几乎认为这是原因:

当您查看Java字节码时,您可能会问有关其他指令的相同问题.为什么在将两个int推入堆栈并将它们立即作为单个long对待时,验证程序会阻止您? (尝试一下,它会阻止您.)您可能会争辩说,通过允许这样做,您可以使用较小的指令集来表达相同的逻辑. (要进一步讲这个参数,一个字节不能表示太多指令,因此应尽可能减少Java字节代码集.)

When you look at Java byte code, you could ask the same question about other instructions. Why would the verifier stop you when pushing two ints on the stack and treating them as a single long right after? (Try it, it will stop you.) You could argue that by allowing this, you could express the same logic with a smaller instruction set. (To go further with this argument, a byte cannot express too many instructions, the Java byte code set should therefore cut down wherever possible.)

当然,从理论上讲,您不需要字节码指令即可将intlong推入堆栈,并且您对INVOKESPECIAL和<不需要两条指令这一事实是正确的. c13>以表示方法调用.方法由其方法描述符(名称和原始参数类型)唯一标识,并且您不能在同一类中同时定义具有相同描述的静态和非静态方法.并且为了验证字节码,Java编译器必须检查目标方法是否仍然是static.

Of course, in theory you would not need a byte code instruction for pushing ints and longs to the stack and you are right about the fact that you would not need two instructions for INVOKESPECIAL and INVOKESTATIC in order to express method invocations. A method is uniquely identified by its method descriptor (name and raw argument types) and you could not define both a static and a non-static method with an identical description within the same class. And in order to validate the byte code, the Java compiler must check whether the target method is static nevertheless.

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

Remark: This contradicts the answer of v6ak. However, a methods descriptor of a non-static method is not altered to include a reference to this.getClass(). The Java runtime could therefore always infer the appropriate method binding from the method descriptor for a hypothetical INVOKESMART instruction. See JVMS §4.3.3.

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

So much for the theory. However, the intentions that are expressed by both invocation types are quite different. And remember that Java byte code is supposed to be used by other tools than javac to create JVM applications, as well. With byte code, these tools produce something that is more similar to machine code than your Java source code. But it is still rather high level. For example, byte code still is verified and the byte code is automatically optimized when compiled to machine code. However, the byte code is an abstraction that intentionally contains some redundancy in order to make the meaning of the byte code more explicit. And just like the Java language uses different names for similar things to make the language more readable, the byte code instruction set contains some redundancy as well. And as another benefit, verification and byte code interpretation/compilation can speed up since a method's invocation type does not always need to be inferred but is explicitly stated in the byte code. This is desirable because verification, interpretation and compilation are done at runtime.

作为最后的轶事,我应该提到在Java 5之前没有将类的静态初始化器<clinit>标记为static.在这种情况下,也可以通过方法的名称来推断静态调用,但这会导致更多的错误.运行时间开销.

As a final anecdote, I should mention that a class's static initializer <clinit> was not flagged static before Java 5. In this context, the static invocation could also be inferred by the method's name but this would cause even more run time overhead.

这篇关于JVM为什么同时具有`invokespecial`和`invokestatic`操作码?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆