简体   繁体   中英

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. Bytecode operation of Child#test is as following.

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. or, Child.hashCode() called, then Child.toString() should be called.

However, invokevirtual does not keep its consistency if and only if target method is inherited by Object.

Only that case, invokevirtual calls method in the Object. For other cases, invokevirtual calls method in current class.

I want to know why this happens.

According to JVM specification p. 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. 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:

  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.

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

Javac probably hard coded this optimization, instead of analyzing code to see if it's really needed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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