When the program is running, method calling is the most common and frequent operation
Method calling is not equal to method execution:
The only task in the method calling phase is to determine the method version being called, that is, which method is called
Does not involve the specific running process inside the method
The compilation process of the Class file does not include the connection step in traditional compilation
All method calls in the Class file are symbolic references stored in the Class file, and It is not the entry address of the method in the actual running memory layout, that is, the previous direct reference:
This makes Java have more powerful dynamic expansion capabilities
At the same time, it also makes the Java method calling process relatively complicated.
It is necessary to determine the direct reference of the target method during class loading or even during running time
The target method in all method calls is a reference to a constant pool in the Class file
In the loading and analysis phase of the class, it will Convert some of the symbol references into direct references:
The method has a determinable calling version before the program is actually executed, and the calling version of this method is unchangeable during runtime
In other words, the calling target is completed in the program code and must be determined when the compiler compiles. This is also called method analysis
In Java, it conforms to the "compile time" It can be seen that there are two major categories of methods that are "immutable during runtime":
Static methods: are directly related to the type
Private method: Not accessible externally
The characteristics of these two methods determine that neither method can be overridden through inheritance or other methods. version, so it is suitable for parsing during the class loading phase
Non-virtual method: During the class loading phase, the symbol reference will be parsed as a direct reference to the method
Static method
Private method
Instance constructor
Parent class method
Virtual method: During the class loading phase, the symbol reference will not be resolved as a direct reference to the method
Except for the above non-virtual methods, other All methods are virtual methods
public class StaticDispatch { static abstract class Human { } static class Man extends Human { } static class Woman extends Human { } public static void sayHello(Human guy) { System.out.println("Hello, Guy!"); } public static void sayHello(Man guy) { System.out.println("Hello, Gentleman!"); } public static void sayHello(woman guy) { System.out.println("Hello, Lady!"); } public static void main(String[] args) { Human man = new Man(); Human women = new Woman(); sayHello(man); sayHello(woman); } }
Human man = new Human();
Human
is the static type of the variable
Man
is the actual type of the variable
Both the static type and the actual type will change in the program:
Static type:
Static type changes only occur when using the
variable The static type itself will not be changed
The final static type is known in the compiler
Actual type:
The result of the actual type change is determined during runtime
The compiler does not know what the actual type of an object is during compilation
Human human = new Man(); sayHello(man); sayHello((Man)man); // 类型转换,静态类型变化,转型后的静态类型一定是Man man = new woman(); // 实际类型变化,实际类型是不确定的 sayHello(man); sayHello((Woman)man); // 类型转换,静态类型变化
When overloading, the compiler uses the static type of the parameter instead of the actual type as the basis for judgment. The static type can be known during compilation:
Compilation phase, The Javac compiler will decide which overloaded version to use based on the static type of the parameter
Static dispatch:
All methods that rely on static types to locate methods Execution version of dispatch action
Typical application: Method overloading
Static dispatch occurs during the compilation phase , Therefore, the action of determining static dispatch is not performed by the virtual machine, but is completed by the compiler.
Since the literal does not display a static type, it can only be understood and inferred through the rules of the language.
public class LiteralTest { public static void sayHello(char arg) { System.out.println("Hello, char!"); } public static void sayHello(int arg) { System.out.println("Hello, int!"); } public static void sayHello(long arg) { System.out.println("Hello, long!"); } public static void sayHello(Character arg) { System.out.println("Hello, Character!"); } public static void main(String[] arg) { sayHello('a'); } }
The compiler annotates the overloaded methods from top to bottom to get different outputs
If the compiler cannot determine which type to customize the conversion to, it will prompt type ambiguity and reject Compile
public class LiteralTest { public static void sayHello(String arg) { // 新增重载方法 System.out.println("Hello, String!"); } public static void sayHello(char arg) { System.out.println("Hello, char!"); } public static void sayHello(int arg) { System.out.println("Hello, int!"); } public static void sayHello(long arg) { System.out.println("Hello, long!"); } public static void sayHello(Character arg) { System.out.println("Hello, Character!"); } public static void main(String[] args) { Random r = new Random(); String s = "abc"; int i = 0; sayHello(r.nextInt() % 2 != 0 ? s : 1 ); // 编译错误 sayHello(r.nextInt() % 2 != 0 ? 'a' : false); //编译错误 } }
public class DynamicDispatch { static abstract class Human { protected abstract void sayHello(); } static class Man extends Human { @override protected void sayHello() { System.out.println("Man Say Hello!"); } } static class Woman extends Human { @override protected void sayHello() { System.out.println("Woman Say Hello!"); } } public static void main(String[] args) { Human man = new Man(); Human women = new Woman(); man.sayHello(); woman.sayHello(); man = new Woman(); man.sayHello(); } }
This is not determined based on the static type
Static typeHumanTwo variablesman and woman perform different behaviors when calling the sayHello() method
VariablesmanDifferent methods were executed in two calls
The reason for this phenomenon: The actual types of the two variables are different
How the Java virtual machine dispatches the execution version of the method according to the actual type: Starting from the polymorphic search process of the invokevirtual instruction , the runtime parsing process of the invokevirtual instruction It is roughly divided into the following steps:
Find the actual type of the object pointed to by the first element on the top of the operand stack, recorded as C
If a method matching the descriptor and simple name in the constant is found in type C, then access permission verification is performed. If the verification is passed, a direct reference to this method is returned, and the search process ends; If the verification fails, java.lang.illegalAccessErrorException
If not found, each type C element will be checked from bottom to top according to the inheritance relationship. The parent class performs the second step of search and verification process
If no suitable method is found, it will throw java.lang.AbstractMethodErrorException
invokevirtualThe first step in instruction execution is to determine the actual status of the receiver at runtime type, so the invokevirtual instruction in the two calls resolves the class method symbol reference in the constant pool to a different direct reference.
This is a dispatch process that determines the method execution version based on the actual type during runtime. It’s called dynamic dispatch
The modes of virtual machine concept analysis are static dispatch and dynamic dispatch. It can be understood that the virtual machine will "will What to do"This question
Virtual machine"How to do it specifically" There will be differences in various virtual machine implementations:
Because dynamic dispatch is a very frequent action, and the method version selection process of dynamic dispatch requires searching for a suitable target method in the method metadata of the class at runtime
Therefore, in the actual implementation of virtual machines, for performance reasons, most implementations will not actually perform such frequent searches
The most commonly used "stability optimization" The way is to create a virtual method table (vtable) for the class in the method area, use virtual method table index instead of metadata search to improve performance
The virtual method table stores the actual entry address of each method:
If a method is not called in the subclass If rewritten, the address entry in the virtual method table of the subclass is consistent with the address entry of the same method in the parent class, and both point to the actual entry of the parent class
If there is a duplicate in the subclass After writing this method, the address in the subclass method table will be replaced with the entry address pointing to the actual method of the subclass
Methods with the same signature, in the parent class, subclass The virtual method table of the class has the same index number:
In this way, when the type is changed, only the method table to be searched needs to be changed, and the required index can be converted from different virtual method tables according to the index. Entry address
The method table is generally initialized in the connection phase of the class loading phase:
After preparing the initial value of the variable of the class, the virtual machine will Also initialized
The above is the detailed content of Using Java method calls to resolve static and dynamic dispatch. For more information, please follow other related articles on the PHP Chinese website!