Implementation principle of dynamic call polymorphism (override) in java method calling -- method table (III)
In the last two blog posts, we discussed java's overload and override, static assignment, and dynamic assignment. This blog post discusses how to implement dynamic assignment, that is, the implementation principle of multi-state override.
Calling principles of overloading and rewriting for java method calls (1)
Most of the content in this article comes from the implementation and comparison of IBM's blog polymorphism in Java and C ++ programming languages. I wrote it here mainly to deepen my understanding, facilitate future viewing, and add some of my own opinions and text organization. It is not for commercial purposes. If you need to deprecate it, please let us know.
Conclusion
Base-class calls and interface-based calls have higher performance.. Because invokevirtual searches for methods based on offsets, while invokeinterface searches for methods based on offsets.
Overview
Polymorphism is an important feature of object-oriented programming. Polymorphism allows the reference of the base class to point to the object of the derived class, and the dynamic binding of methods during specific access.
Java implements dynamic method binding based on the method table, but there are two call Methods: invokevirtual and invokeinterface, that is, class reference call and interface Reference call. To call a class reference, you only need to modify the pointer of the method table to realize dynamic binding (the method with the same signature has the same index number in the method table of the parent class and subclass ), the interface Reference call requires scanning the entire method table to implement dynamic binding (because one class can implement multiple interfaces, and the other class may only implement one interface and cannot have the same index number. If you do not understand this sentence, you can continue to look at it as an example. When I write this article, I feel that when I read a book, I sometimes don't understand it. I still don't understand it for a while. Just mark it and read it again,PossibleIt is suddenly open .).
The general process of class reference calling is: the java compiler compiles the java source code into a class file. During the compilation process, the referenced symbols of the call are written to the class file according to the static type. During execution, JVM finds the symbolic reference of the calling method based on the class file, finds the offset in the static type method table, and then determines the actual object type based on this pointer, the actual type of method table is used. The offset is the same as the offset of the method table in the static type. If this method is found in the actual type of method table, it is called directly. Otherwise, search from bottom up based on the inheritance relationship.
The following is a detailed analysis and discussion of the above description.
JVM runtime Structure
It can be seen that when a program runs and requires a class, the class loading subsystem will load the corresponding class file to the JVM and create the class type information internally, this type of information is actually a Data Structure Stored in the class file in JVM. It contains all the information defined by the java class, including the method code, class variables, member variables, and content to be discussed in this blogMethod table <喎?http: www.bkjia.com kf ware vc " target="_blank" class="keylink"> Release + release/release + release/aOoy/release/fs8y5ss/ttcTE2rTmx/release + NTattHW0L/release + ioaO + zc/release + i0rvR + aGjPGJyIC8 + authorization + authorization/yt723qLT6wuu1xNa41euho9Xi0Km3vbeo1tCw/MCotNO4uMDgvMyz0LXEy/authorization = "class reference call invokevirtual"> class reference call invokevirtual
The Code is as follows:
package org.fan.learn.methodTable;/** * Created by fan on 2016/3/30. */public class ClassReference { static class Person { @Override public String toString(){ return "I'm a person."; } public void eat(){ System.out.println("Person eat"); } public void speak(){ System.out.println("Person speak"); } } static class Boy extends Person{ @Override public String toString(){ return "I'm a boy"; } @Override public void speak(){ System.out.println("Boy speak"); } public void fight(){ System.out.println("Boy fight"); } } static class Girl extends Person{ @Override public String toString(){ return "I'm a girl"; } @Override public void speak(){ System.out.println("Girl speak"); } public void sing(){ System.out.println("Girl sing"); } } public static void main(String[] args) { Person boy = new Boy(); Person girl = new Girl(); System.out.println(boy); boy.eat(); boy.speak(); //boy.fight(); System.out.println(girl); girl.eat(); girl.speak(); //girl.sing(); }}
Note,boy.fight();
Andgirl.sing();
The two are problematic. In IDEA, "Cannot resolve method 'Fight () '" is displayed ()'". Because there is a static type check for method calls, while the static types of boy and girl are all Person types, there is no fight method or sing method in Person. Therefore, an error is reported.
The execution result is as follows:
As you can see,boy.eat()
Andgirl.eat()
The output produced by the call is "Person eat", because there is no eat Method for override parent class in Boy and Girl.
Bytecode instructions:
public static void main(java.lang.String[]); Code: Stack=2, Locals=3, Args_size=1 0: new #2; //class ClassReference$Boy 3: dup 4: invokespecial #3; //Method ClassReference$Boy." ":()V 7: astore_1 8: new #4; //class ClassReference$Girl 11: dup 12: invokespecial #5; //Method ClassReference$Girl." ":()V 15: astore_2 16: getstatic #6; //Field java/lang/System.out:Ljava/io/PrintStream; 19: aload_1 20: invokevirtual #7; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V 23: aload_1 24: invokevirtual #8; //Method ClassReference$Person.eat:()V 27: aload_1 28: invokevirtual #9; //Method ClassReference$Person.speak:()V 31: getstatic #6; //Field java/lang/System.out:Ljava/io/PrintStream; 34: aload_2 35: invokevirtual #7; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V 38: aload_2 39: invokevirtual #8; //Method ClassReference$Person.eat:()V 42: aload_2 43: invokevirtual #9; //Method ClassReference$Person.speak:()V 46: return
All invokevirtual calls the methods in the Person class.
Let's take a look at the memory model of the java object:
You can clearly see the pointer to the call method. It can be seen that the offset of the same signature method in the method table is the same. This offset only indicates that the offset of the methods inherited from the Object class in the Boy method table and the method inherited from the Person class is the same as the offset of the same method in the Person class, it has nothing to do with Girl.
Next let's take a look at the call processgirl.speak()
The call method is used as an example. In my bytecode, this command corresponds43: invokevirtual #9; //Method ClassReference$Person.speak:()V
To facilitate the use of IBM charts, here we use the same symbol reference as IBM:invokevirtual #12;
. The call process is shown as follows:
(1) locate the symbolic reference of the method call in the constant pool
(2) view the method table of Person and obtain the offset of the speak method in this method table (assuming 15). Then, the direct reference of this method is obtained.
(3) determine the actual type of the method receiver (girl) based on the this pointer
(4) obtain the method table corresponding to the actual type of the object, and check whether there is override according to the offset 15. If the method is overwritten, you can directly call it; if not overwritten, You need to obtain the base class (here is the Person class) from the bottom according to the inheritance relationship, and check whether this method is available according to the offset 15.
Call invokeinterfaceThe Code is as follows:
package org.fan.learn.methodTable;/** * Created by fan on 2016/3/29. */public class InterfaceReference { interface IDance { void dance(); } static class Person { @Override public String toString() { return "I'm a person"; } public void speak() { System.out.println("Person speak"); } public void eat() { System.out.println("Person eat"); } } static class Dancer extends Person implements IDance { @Override public String toString() { return "I'm a Dancer"; } @Override public void speak() { System.out.println("Dancer speak"); } public void dance() { System.out.println("Dancer dance"); } } static class Snake implements IDance { @Override public String toString() { return "I'm a Snake"; } public void dance() { System.out.println("Snake dance"); } } public static void main(String[] args) { IDance dancer = new Dancer(); System.out.println(dancer); dancer.dance(); //dancer.speak(); //dancer.eat(); IDance snake = new Snake(); System.out.println(snake); snake.dance(); }}
In the above Codedancer.speak(); dancer.eat();
These two statements cannot be called.
The execution result is as follows:
Its bytecode commands are as follows:
public static void main(java.lang.String[]); Code: Stack=2, Locals=3, Args_size=1 0: new #2; //class InterfaceReference$Dancer 3: dup 4: invokespecial #3; //Method InterfaceReference$Dancer."
":()V 7: astore_1 8: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream; 11: aload_1 12: invokevirtual #5; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V 15: aload_1 16: invokeinterface #6, 1; //InterfaceMethod InterfaceReference$IDance.dance:()V 21: new #7; //class InterfaceReference$Snake 24: dup 25: invokespecial #8; //Method InterfaceReference$Snake."
":()V 28: astore_2 29: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream; 32: aload_2 33: invokevirtual #5; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V 36: aload_2 37: invokeinterface #6, 1; //InterfaceMethod InterfaceReference$IDance.dance:()V 42: return
From the above bytecode instructions, we can see that,dancer.dance();
Andsnake.dance();
All the bytecode commands areinvokeinterface #6, 1; //InterfaceMethod InterfaceReference$IDance.dance:()V
.
Why does the invokeinterface command have two parameters?
The object memory model is as follows:
The offset of the method dance () in the IDance interface in the method table of the Dancer class is different from that in the method table of the Snake class, therefore, you cannot call a method based on the offset. (Note this sentence to emphasize that invokeinterface is not implemented based on offset when searching a method, but based on search .) In this case, the offset of the dance method in the IDance method table (if any) is different from that in the Dancer method table.
Therefore, to find the dance method in the Dancer method table, you must search the entire Dancer method table.
Write one below. What will happen if the Dancer does not override the toString method?
The Code is as follows:
package org.fan.learn.methodTable;/** * Created by fan on 2016/3/29. */public class InterfaceReference { interface IDance { void dance(); } static class Person { @Override public String toString() { return "I'm a person"; } public void speak() { System.out.println("Person speak"); } public void eat() { System.out.println("Person eat"); } } static class Dancer extends Person implements IDance {// @Override// public String toString() {// return "I'm a Dancer";// } @Override public void speak() { System.out.println("Dancer speak"); } public void dance() { System.out.println("Dancer dance"); } } static class Snake implements IDance { @Override public String toString() { return "I'm a Snake"; } public void dance() { System.out.println("Snake dance"); } } public static void main(String[] args) { IDance dancer = new Dancer(); System.out.println(dancer); dancer.dance(); //dancer.speak(); //dancer.eat(); IDance snake = new Snake(); System.out.println(snake); snake.dance(); }}
The execution result is as follows:
We can see thatSystem.out.println(dancer);
The toString method of Person is called.
The memory model is as follows:
ConclusionThis blog post discusses the differences between the internal implementation of invokevirtual and invokeinterface, And the implementation principle of override. Next, I plan to discuss the specific implementation details of invokevirtual, such as: how to implement conversion from symbol reference to direct reference? May look at the underlying C ++ Implementation of OpenJDK.