简体   繁体   English

Java字节码操作'invokevirtual'不保持Object继承的方法的一致性

[英]Java bytecode operation 'invokevirtual' does not keep consistency for the methods inherited by Object

I have following codes. 我有以下代码。

public class Parent {

    @Override
    public int hashCode() {
         return 0;
    }

}

public class Child extends Parent {

    public void test() {
        this.toString();
        this.hashCode();
    }

}

As you see in the above codes, Child inherits toString() from Object and hashCode() from Parent. 正如您在上面的代码中看到的那样,Child从Object继承toString(),从Parent继承hashCode()。 Bytecode operation of Child#test is as following. Child#test的字节码操作如下。

ALOAD 0: this
INVOKEVIRTUAL Object.toString() : String
ALOAD 0: this
INVOKEVIRTUAL Child.hashCode() : int
RETURN

I think if invokevirtual calls Object.toString(), it should call Parent.hashCode() for consistency. 我认为如果invokevirtual调用Object.toString(),它应该调用Parent.hashCode()以保持一致性。 or, Child.hashCode() called, then Child.toString() should be called. 或者,调用Child.hashCode(),然后调用Child.toString()。

However, invokevirtual does not keep its consistency if and only if target method is inherited by Object. 但是,当且仅当Object继承目标方法时,invokevirtual才会保持其一致性。

Only that case, invokevirtual calls method in the Object. 只有这种情况,在Object中调用虚拟调用方法。 For other cases, invokevirtual calls method in current class. 对于其他情况,请在当前类中调用虚拟调用方法。

I want to know why this happens. 我想知道为什么会这样。

According to JVM specification p. 根据JVM规范p。 3.7 : 3.7

The compiler does not know the internal layout of a class instance. 编译器不知道类实例的内部布局。 Instead, it generates symbolic references to the methods of an instance, which are stored in the runtime constant pool. 相反,它生成对实例方法的符号引用,这些引用存储在运行时常量池中。 Those runtime constant pool items are resolved at runtime to determine the actual method location. 在运行时解析这些运行时常量池项以确定实际的方法位置。

It means that all these symbolic Child.hashCode() are only constants, which are not specifying some kind of how JVM calls this methods. 这意味着所有这些符号的Child.hashCode()只是常量,它们没有指定JVM如何调用此方法。 It seems, that for toString() method compiler knows, that this method has its base implementation in Object class, so it puts symbolic constant to Object class in constant pool - this is some kind of optimization, which makes compiler for JVM: 看来, toString()方法编译器知道,这个方法在Object类中有它的基本实现,所以它将常量池中的符号常量放到Object类中 - 这是一种优化,这使得JVM的编译器:

  Constant pool:
const #2 = Method   #24.#25;    //  java/lang/Object.toString:()Ljava/lang/String;
...
const #24 = class   #34;    //  java/lang/Object
const #25 = NameAndType #35:#36;//  toString:()Ljava/lang/String;

You are correct that the compiler behaves unlogical. 你是正确的,编译器的行为是非逻辑的。 But the effect of this code is identical to the effect of both variants that you suggested. 但是此代码的效果与您建议的两种变体的效果相同。 So this is likely not an intentional behaviour, but result of long evolution of the code of the compiler. 所以这可能不是故意的行为,而是编译器代码长期演变的结果。 Other compilers may produce different code. 其他编译器可能会生成不同的代码。

My theory: toString() is used very frequently, so javac use the common Object.toString() to save constant pool entries. 我的理论: toString()经常使用,因此javac使用公共Object.toString()来保存常量池条目。

For example if the code contains foo.toString() and bar.toString() , the contant pool only needs one Object.toString , instead of two entries Foo.toString and Bar.toString 例如,如果代码包含foo.toString() and bar.toString() ,则只需要一个Object.toString ,而不是两个条目Foo.toString and Bar.toString

Javac probably hard coded this optimization, instead of analyzing code to see if it's really needed. Javac可能硬编码了这种优化,而不是分析代码以确定它是否真的需要。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM