This is the 27th day of my participation in Gwen Challenge
The method call
- Making method calls is the most common and frequent operation when a program is running
- Method invocation does not equal method execution:
- The only task in the method invocation phase is to determine the version of the method being invoked, that is, which method to call
- There is no mention of the actual running process inside the method
- The process of compiling a Class file does not include the join step in traditional compilation
- All method calls in the Class file store symbolic references in the Class file, rather than the entry address of the method in the actual runtime memory layout, i.e. the previous direct reference:
- This makes Java more capable of dynamic extension
- It also makes the Java method call process relatively complicated
- A direct reference to the target method needs to be determined during class loading and even at runtime
Method resolution
- The target method in all method calls is a reference to a constant pool in the Class file
- During the class load parsing phase, some symbolic references are converted to direct references:
- Methods have a determinable invocation version before the program actually executes, and the invocation version of this method is immutable at run time
- That is, the target of the call is done in the program code and must be determined by the compiler when it is compiled, which is also called method resolution
Java method classification
- In Java“Known at compile time, immutable at run time.”There are two broad categories of methods:
- Static methods: Directly associated with the type
- Private methods: cannot be accessed externally
- The nature of these two methods makes it impossible to override the version by inheritance or other means, so they are suitable for parsing during class loading
- Non-virtual methods:Symbolic references are resolved to direct references to the method during class loading
- A static method
- Private methods
- Instance constructor
- The parent class method
- Virtual method:Symbolic references are not resolved to direct references to the method during class loading
- Except for the above non-virtual methods, all other methods are virtual methods
Static dispatching
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 = newWoman(); sayHello(man); sayHello(woman); }}Copy the code
Human man = new Human();
- Human is the static type of the variable
- Man is the actual type of the variable
- Both static and actual types can be changed in the program:
- Static type:
- Static type changes occur only when they are used
- The static type of the variable itself is not changed
- The final static type is known in the compiler
- Actual type:
- The result of the actual type change is determined at run time
- The compiler doesn’t know at compile time what the actual type of an object is
- Static type:
Human human = new Man();
sayHello(man);
sayHello((Man)man); // The static type must be Man
man = new woman(); // The actual type changes. The actual type is uncertain
sayHello(man);
sayHello((Woman)man); // Static type change
Copy the code
- The compiler relies on the static type of the argument rather than the actual type when reloading. Static types are known at compile time:
- At compile time, the Javac compiler decides which overloaded version to use based on the static type of the parameter
- Static dispatch:
- All dispatch actions that rely on static types to locate the execution version of a method
- Typical use: method overloading
- Static dispatch occurs at compile time, so the action to determine static dispatch is not performed by the virtual machine, but by the compiler
- Because literals do not display static typing, they can only be understood and inferred by linguistic rules
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'); }}Copy the code
- The compiler annotates the overloaded methods from top to bottom to get different output
- If the compiler cannot determine which type to convert to, it will indicate type ambiguity and refuse compilation
public class LiteralTest {
public static void sayHello(String arg) { // Add an overloaded method
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 ); // Error compiling
sayHello(r.nextInt() % 2! =0 ? 'a' : false); // Error compiling}}Copy the code
Dynamic dispatch
public class DynamicDispatch {
static abstract class Human {
protected abstract void sayHello(a);
}
static class Man extends Human {
@override
protected void sayHello(a) {
System.out.println("Man Say Hello!"); }}static class Woman extends Human {
@override
protected void sayHello(a) {
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 = newWoman(); man.sayHello(); }}Copy the code
- This is not based on static typing
- The statically typed Human variables man and woman perform different behaviors when calling the sayHello() method
- The variable man executes different methods in the two calls
- The reason for this is that the actual types of the two variables are different
- How the Java virtual machine assigns the execution version of a method based on the actual type: 从invokevirtualThe polymorphic lookup process of the instruction begins,invokevirtualThe instruction runtime parsing process can be roughly divided into the following steps:
- Find the actual type of the object pointed to by the first element at the top of the operand stack. Call it C
- If a method of type C is found that matches the descriptor and simple name in the constant, then the access permission is verified. If the verification succeeds, a direct reference to the method is returned, and the search process is complete. If verification is not through, it throws the Java. Lang. IllegalAccessError anomalies
- If not found, the second step of the search and verification process is carried out for each parent class of type C from bottom to top according to the inheritance relationship
- If didn’t find the right way, it throws the Java. Lang. AbstractMethodError anomalies
- The nature of Java language method rewriting:
- The first step in the invokevirtual directive execution is to determine the actual type of recipient at runtime, so the Invokevirtual directive in both calls resolves the class method symbol references in the constant pool into different direct references
- This dispatch process, which determines the version of method execution at run time based on the actual type, is called dynamic dispatch
Virtual machine dynamic dispatch implementation
- The mode of virtual machine concept resolution is static dispatch and dynamic dispatch. Can you understand the question “what will a virtual machine do” in dispatch
- The virtual machine“How exactly did it happen?”There will be differences between different virtual machine implementations:
- Because dynamic dispatch is a very frequent action, the method version selection process for dynamic dispatch requires the runtime to search the class’s method metadata for the appropriate target method
- Therefore, in the actual implementation of virtual machines, most implementations do not really search this frequently for performance reasons
- The most common way of “stable optimization” is to create one for a class in the method areaVirtual Method Table (VTable),useVirtual method table indexes replace metadata lookupsTo improve performance
- The virtual method table contains the actual entry address of each method:
- If a method is not overridden in a subclass, the address entry in the virtual method table of the subclass is the same as the address entry of the same method in the parent class, pointing to the actual entry of the parent class
- If a subclass overrides the method, the address in the subclass’s method table is replaced by the entry address pointing to the subclass’s actual method
- Methods with the same signature have the same index number in the virtual method table of the parent and child classes:
- In this way, when the type is changed, only the method table is changed, and the desired entry address can be indexed from the different virtual method table
- The method table is typically initialized during the join phase of the class load phase:
- Once you have prepared the class’s initial variable values, the virtual machine also initializes the class’s method table
- The virtual method table contains the actual entry address of each method: