JVM method invocation
Method invocation is not equal to method execution. The goal of the method invocation phase is to determine which method is called. The target method in all method invocations is a symbolic reference in a constant pool in the Class file. In the class loading phase, it will be Converting some of the symbolic references into direct references, this kind of resolution can be established if the method has a determinable invocation version before the program runs, and the invocation version of this method is immutable during runtime. The invocation of such methods is called parsing.
Instructions to call the method:
invokestatic:Invoke a class (static
) method
invokespecial : Invoke instance method; special handling for superclass, private, and instance initialization method invocations parent class method (that is, super.methodName() call method), private method and constructor
invokevirtual:Invoke instance method; dispatch based on class
invokeinterface:Invoke interface method
invokedynamic:Invoke dynamic method
As long as the methods that can be invoked by the invokestatic and invokespecial instructions can determine the unique invocation version in the parsing phase, including static methods, private methods, constructors, and parent class methods, these methods will resolve the symbolic reference when the class is loaded. for direct reference.
1. Static dispatch
Look at the following code and its output:
public class StaticDispatch { static class Animal {} static class Cat extends Animal{} static class Dog extends Animal {} public void method(Animal animal) { System.out.println("animal"); } public void method(Cat cat) { System.out.println("cat"); } public void method(Dog dog) { System.out.println("dog"); } public static void main(String[] args) { StaticDispatch ref = new StaticDispatch(); Animal animal = new Animal(); Animal cat = new Cat(); Animal dog = new Dog(); ref.method(animal); //output animal ref.method(cat); //output animal ref.method(dog); //output animal } }
For Animal cat = new Cat(), Animal is called the static type (Static Type) of the cat variable, and Cat is called the actual type (Actual Type) of the cat variable. The static type of the variable itself will not change, but the actual type can be Changes, such as cat = new Dog(), can change the actual type of the cat variable to Dog, the static type is known at compile time, and the actual type is known at runtime.
Look at the bytecode of the main method:
public static void main(java.lang.String[]); Code: Stack=2, Locals=5, Args_size=1 0: new #1; //class cc/lixiaohui/demo/dispatch/StaticDispatch 3: dup 4: invokespecial #41; //Method "<init>":()V 7: astore_1 8: new #42; //class cc/lixiaohui/demo/dispatch/StaticDispatch$Animal 11: dup 12: invokespecial #44; //Method cc/lixiaohui/demo/dispatch/StaticDispatch$Animal."<init>":()V 15: astore_2 16: new #45; //class cc/lixiaohui/demo/dispatch/StaticDispatch$Cat 19: dup 20: invokespecial #47; //Method cc/lixiaohui/demo/dispatch/StaticDispatch$Cat."<init>":()V 23: astore_3 24: new #48; //class cc/lixiaohui/demo/dispatch/StaticDispatch$Dog 27: dup 28: invokespecial #50; //Method cc/lixiaohui/demo/dispatch/StaticDispatch$Dog."<init>":()V 31: astore 4 33: aload_1 34: aload_2 35: invokevirtual #51; //Method method:(Lcc/lixiaohui/demo/dispatch/StaticDispatch$Animal;)V 38: aload_1 39: aload_3 40: invokevirtual #51; //Method method:(Lcc/lixiaohui/demo/dispatch/StaticDispatch$Animal;)V 43: aload_1 44: aload 4 46: invokevirtual #51; //Method method:(Lcc/lixiaohui/demo/dispatch/StaticDispatch$Animal;)V 49: return LineNumberTable: line 27: 0 line 29: 8 line 30: 16 line 31: 24 line 33: 33 line 34: 38 line 35: 43 line 36: 49 LocalVariableTable: Start Length Slot Name Signature 0 50 0 args [Ljava/lang/String; 8 42 1 ref Lcc/lixiaohui/demo/dispatch/StaticDispatch; 16 34 2 animal Lcc/lixiaohui/demo/dispatch/StaticDispatch$Animal; 24 26 3 cat Lcc/lixiaohui/demo/dispatch/StaticDispatch$Animal; 33 17 4 dog Lcc/lixiaohui/demo/dispatch/StaticDispatch$Animal; }
You can see that there are three invokevirtual instructions in lines 35-46, which are actions to call three methods respectively. You can see that the target methods are the same: Method method:(Lcc/lixiaohui/demo/dispatch/StaticDispatch$Animal;)V .
invokevirtual instruction
parameters ..., objectref , [ arg1 , [ arg2 ...]] →
Invoke instance method; dispatch based on class, call instance method
Let C be the class of objectref. The actual method to be invoked is selected by the following lookup procedure:
- If C contains a declaration for an instance method
m
that overrides (§5.4.5) the resolved method, thenm
is the method to be invoked, and the lookup procedure terminates. - Otherwise, if C has a superclass, this same lookup procedure is performed recursively using the direct superclass of C; the method to be invoked is the result of the recursive invocation of this lookup procedure.
- Otherwise, an
AbstractMethodError
is raised.
Therefore, in the process of calling the above code, objectref is ref, and jvm goes to ref to refer to the class of the object (that is, the StaticDispatch class) to find the method whose descriptor is (Lcc/lixiaohui/demo/dispatch/StaticDispatch$Animal;)V, very The obvious method method(Animal animal) meets the requirements, and the descriptor of method(Cat cat) is (Lcc/lixiaohui/demo/dispatch/StaticDispatch$Cat;)V, and the descriptor of method(Dog dog) is (Lcc/lixiaohui/ demo/dispatch/StaticDispatch$Dog;)V, none of them meet the requirements.
When overloading, the dispatch lookup is based on the static type rather than the actual type.
2. Dynamic allocation
Look at the code and its result:
public class DynamicDispatch { static class Animal{ void say(){ System.out.println("animal"); } } static class Cat extends Animal { void say() { System.out.println("cat"); } } static class Dog extends Animal { void say() { System.out.println("dog"); } } public static void main(String[] args) { Animal cat = new Cat(); Animal dog = new Dog(); cat.say(); // cat dog.say(); // dog cat = new Dog(); cat.say(); // dog } }
The say() method is an instance method, so it is also called by the invokevirtual instruction. As long as the dispatching process of the invokevirtual instruction mentioned above is applied, you can know the implementation principle of Java's polymorphism. For example, when cat.say() is executed (the method descriptor For say:()V), the jvm goes to the class of the object referenced by cat (that is, the actual type Actual Type), that is, the same method for finding the descriptor in the Cat class (instead of Animal), obviously you can find Cat.say( ) method, so the method is called. The inheritance relationship here is relatively simple, but even if the inheritance relationship is very complicated, the routine is still this routine. Start looking for the actual type, if you can't find it, go to the parent class to find it, recursively find it... Finally, if you don't find it, throw an exception.
Override is by actual type instead of static type as the basis for dispatch lookup
refer to:
"In-depth understanding of virtual machines"
http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokevirtual