The method invocation is not equivalent to method execution, and the only task in the method invocation phase is to determine the version of the calling method (that is, which method is called), and for the time being, does not involve a specific running procedure inside the method. Making method calls is the most common and frequent operation when the program is running. During the compilation of the class file, the connection steps in the traditional compilation are not included, and all method calls are stored in the class file only as symbolic references, not as entry addresses (equivalent to direct references) in the memory layout when the method is actually running. This feature gives Java more dynamic scalability, but it also makes the process of calling Java methods relatively complex and requires the direct reference of the target method to be determined during class loading and even during runtime.
First, method analysis
The target method in all method calls is a symbol reference in a constant pool in the class file, and in the parsing phase of the classes loading, some of the symbolic references are converted to direct references, which can be established if the method has a deterministic version of the call before the program actually runs. and the call version of this method is the runtime is immutable. In other words, the invocation target must be determined when the program code is written and the compiler compiles. The invocation of such a method is called parsing (Resolution).
In the Java language, the method that conforms to the requirement of "compile period to see that the runtime is immutable" has two classes of static method and private method, the former is directly related to the type, the latter is not accessible externally, neither of which can be overridden by inheritance or other means. They are therefore suitable for static parsing during the class loading phase.
Corresponding to this, four method call bytecode instructions are provided in the Java Virtual machine, respectively:
A.invokestatic: Calling a static method
B.invokespecial: Invokes instance constructors <init> methods, private methods, and parent class methods.
C.invokevirtual: Call virtual method.
D.invokeinterface: Invokes an interface method that, at run time, determines an object that implements this interface.
As long as the method can be called by the invokestatic and invokespecial instructions, it is possible to determine a unique invocation version in the parsing phase, with static methods, private methods, instance constructors, and parent class methods that conform to this condition. They parse the symbolic reference into a direct reference to the method when the class is loaded. These methods can be collectively referred to as non-virtual methods, and in contrast, other methods are called virtual methods (except the final method).
The non-virtual methods in Java, in addition to the methods invoked with the invokestatic and invokespecial directives, are the final modified methods. Although the final method is invoked with the Invokevirtual directive, it cannot be overwritten, there are no other versions, so there is no need to make a polymorphic selection of the method reception, or the result of a polymorphic selection is unique. The final method is explicitly stated in the Java language Specification as a non-virtual method.
The parsing call must be a static process, fully deterministic during compilation, and the symbolic references involved will all be transformed into deterministic direct references during the parsing phase of the class load, and will not be deferred until the run time. Allocation (Dispatch) calls can be static or dynamic, and can be divided into single and multiple allocations based on the number of parcels on which the allocation is based. These two types of dispatch 22 components constitute static single dispatch, static multi-Dispatch, dynamic single dispatch and dynamic multi-Dispatch.
II. allocation
1. Static Dispatch
Here is a program code:
Package Com.xtayfjpk.jvm.chapter8;public class Staticdispatch {static abstract class Human {}static class man extends Huma n {}static class Woman extends Human {}public void SayHello (Human guy) {System.out.println ("Hello guy ...");} public void SayHello (Mans man) {System.out.println ("Hello man ..."); public void SayHello (Woman Woman) {System.out.println ("Hello Woman ..."); public static void Main (string[] args) {Human mans = new Man (); Human woman = new Woman (); Staticdispatch sd = new Staticdispatch (), Sd.sayhello (man), Sd.sayhello (woman);}}
The result of the execution is:
Hello man ...
Hello guy ...
But why would you choose to execute an overload with a parameter of human? Before that, two important concepts are defined by the following code: Human man = new Man ();
The "Human" in the code above is called the static type of the variable or the appearance type (apparent type), and the following "man" is called the actual type of the variable (Actual type), and the static type and the actual type can vary in the program. The difference is that the static type change occurs only when it is used, the static type of the variable itself is not changed, and the final static type is the compile period, and the result of the actual type change can be determined at runtime, and the compilation period does not know what the actual type of an object is when compiling the program. As in the following code:
The actual type changes human man = new Man (), man = new Woman ();//static type Change Sd.sayhello ((man) man); Sd.sayhello ((Woman) mans);
Explain the two concepts and go back to the code. The two-time SayHello () method call in Main (), with which overloaded version is used, depends entirely on the incoming parameter and data type, if the method receiver has determined that the object is "SR". The code deliberately defines two variables with the same static type and different actual types, but the virtual machine (exactly the compiler) is judged by the static type of the parameter instead of the actual type when overloaded. And the static type is known at compile time, so in the compile phase, the Javac compiler determines which overloaded version to use based on the static type of the parameter, so chooses SayHello (Human) as the invocation target and writes the symbolic reference of the method to main () The parameters of the two invokevirual directives of the method.
All dispatch actions that rely on static types to locate a method's execution version are called static allocations. The most typical application of static dispatch is method overloading. Static dispatch occurs during the compilation phase, so determining the power of static dispatch is not actually performed by the virtual machine. In addition, although the compiler can determine the overloaded version of the method, but in many cases, this overloaded version is not "unique", often can only determine a "more suitable" version. This vague conclusion in the computer world of 0 and 1 is a relatively "rare" event, the main reason for this fuzzy conclusion is that the literal does not need to be defined, so the literal does not have an explicit static type, its static type can only be understood and inferred by the rules of the language.
2. Dynamic Dispatch
Dynamic dispatch is closely related to rewriting (override). The following code:
Package Com.xtayfjpk.jvm.chapter8;public class Dynamicdispatch {static abstract class Human {protected abstract void Sayh Ello ();} Static class Man extends Human {@Overrideprotected void SayHello () {System.out.println (' man say hello ');}} Static Class Woman extends Human {@Overrideprotected void SayHello () {System.out.println ("Woman say Hello");}} public static void Main (string[] args) {Human mans = new Man (); Human woman = new Woman (); Man.sayhello (); Woman.sayhello (); man = new Woman (); Man.sayhello ();}}
It is not possible to determine this in terms of static types, since static types are all two variables of human man and woman perform different behavior when calling the SayHello () method, and the variable man executes different methods in two calls. The original result of this phenomenon is that the actual type of the two variables is different. So how does the Java Virtual machine Dispatch the method execution version according to the actual type, we use the JAVAP command to output the bytecode of this code, the result is as follows:
public static void Main (java.lang.string[]); Flags:acc_public, Acc_static code:stack=2, locals=3, args_size=1 0:new #16// Class Com/xtayfjpk/jvm/chapter8/dynamicdispatch$man 3:dup 4:invokespecial #18//Method Com/xtayfjpk/jvm/chapter8/dynamicdispatch$man. " <init>:() V 7:astore_1 8:new #19//Class Com/xtayfjpk/jvm/chapter8/dyna Micdispatch$woman 11:dup 12:invokespecial #21//Method COM/XTAYFJPK/JVM/CHAPTER8/DYNAMICD Ispatch$woman. " <init> ":() V 15:astore_2 16:aload_1 17:invokevirtual #22//Method COM/XTAYFJ Pk/jvm/chapter8/dynamicdispatch$human.sayhello: () V 20:aload_2 21:invokevirtual #22//Meth OD Com/xtayfjpk/jvm/chapter8/dynamicdispatch$human.sayhello: () V 24:new #19//Class com/ Xtayfjpk/jvm/chapter8/dynamicdispAtch$woman 27:dup 28:invokespecial #21//Method Com/xtayfjpk/jvm/chapter8/dynamicdispatch $Woman. " <init> ":() V 31:astore_1 32:aload_1 33:invokevirtual #22//Method COM/XTAYFJ Pk/jvm/chapter8/dynamicdispatch$human.sayhello: () V 36:return
The 0-15-line bytecode is a prepare action that establishes the memory space of man and woman, invokes the instance constructors of the man and woman classes, and stores the references to the two instances in the 1th and 2nd local variable table slots, which correspond to the two sentences in the code:
Human mans = New Man (); Human woman = new Woman ();
The next 第16-21 line is the key section, with the 16th and 20th lines pressing the reference of the two objects just created to the top of the stack, which is the owner of the SayHello () method to be executed, called the receiver, and the 17th and 21st lines are method invocation directives. From the point of view of bytecode, these two call instructions are exactly the same regardless of whether they are instructions (all invokevirtual) or parameters (both Human.sayhello () in the constant pool), but the target methods that the two directives ultimately execute are not the same. The reason for this needs to start with the Invokevirutal command's polymorphic lookup process, and the runtime parsing process of the invokevirtual instruction is broadly divided into the following steps:
A. Find the actual type of the object pointed to by the first element at the top of the operand stack, as C.
B. If a method is found in type C that is identical to the descriptor and simple name in the constant, the access check is performed, and if passed returns the direct reference to the method, the lookup ends, and the java.lang.IllegalAccessError error is returned without passing.
C. Otherwise, follow the inheritance relationship from the bottom up to the 2nd step of the search and validation process for each parent class C.
D. If the appropriate method is not always found, throw a java.lang.AbstractMethodError error.
Since the first step in the execution of the invokevirtual instruction is to determine the actual type of receiver at run time, the invokevirtual instruction in two calls resolves the class method symbol reference in the constant pool to a different direct reference, which is the essence of the method rewrite in the Java language. We call this dispatch process, which executes versions based on the actual type determination method at run time, as dynamic dispatch.
3. Single dispatch and multiple dispatch
The receiver of the method and the parameters of the method are collectively referred to as the method's volume. Depending on how many kinds of parcels the allocation is based on, the allocation can be divided into single and multi-Dispatch. A single dispatch is a selection of the target method based on one volume, and the multiple allocation selects the target method based on more than one volume.
The static dispatch process at compile time chooses the target method according to two points: one is static type, the other is the method parameter, so the static assignment of the Java language belongs to the multi-dispatch type. The dynamic dispatch process of a virtual machine at run-time can only be selected by the actual type of the receiver as the target method, so the dynamic dispatch of the Java language belongs to the single dispatch type. Where the Java language is a static multi-dispatch, dynamic single-Dispatch language.
4. Implementation of virtual Machine dynamic dispatch
Because dynamic dispatch is a very frequent action, and the dynamically dispatched method version selection process needs to search for the appropriate target method in the method metadata of the class at run time, most implementations do not really perform such a frequent search based on performance considerations in the actual implementation of the virtual machine. In the face of this situation, the most commonly used optimization method is to create a virtual method table (virtual table, also known as vtable) in the class's methods area, which corresponds to the interface method table used in Invokeinterface execution, Interface Table, also known as itable), uses a virtual method table index instead of metadata to find it to improve performance.
The virtual method table holds the actual entry address of each method. If a method is not overridden in a subclass, then the address entry in the virtual method table of the child class and the address entry for the parent class method are consistent, pointing to the implementation entry of the parent class. If this method is overridden in a subclass, the address in the subclass method table will be replaced with the address entry that points to the subclass implementation version.
Deep understanding of Java Virtual Machine notes---method calls