preface
- Now that you’ve learned about class loading and bytecode files, it’s time to learn how the JVM executes code.
The stack frame (stack frame)
- A stack frame is a data structure that helps a virtual machine perform method calls and method executions.
- One stack frame per thread, no concurrency problem.
- The stack frame itself is a kind of data structure, which encapsulates the local variable table, dynamic link information, method return address and operand stack information.
- Some symbolic references are converted to direct references during class loading or the first time they are used. This conversion is calledStatic analysis; Other symbolic references are converted to direct references at each run time, calledDynamic link, which is represented by Java polymorphism.
- Local variable tables have been interpreted at the bytecode level.
- The return address of a method simply means that the method returns to the place where it was called.
- The operand stack is simply a temporary data store where calculations are performed.
- Symbolic references describe the referenced object as a set of symbols.
- A direct reference to a pointer or relative offset directly to the target or a handle that can be indirectly located to the target.
- Incidentally, the concept of Slot is related to the JVM variable Slot.
The method call
Instruction for method invocation
- Invokeinterface: Invokes a method in an interface, which is actually determined at run time to invoke a specific method on which object implements the interface.
- Invokestatic: Invokes static methods.
- Invokespecial: Calls its own private method, constructor (), and parent class method.
- Invokevirtual: invokes virtual methods to run a dynamic lookup process.
- Invokedynamic: Invokes methods dynamically.
Four cases of static parsing:
Static resolution is the ability to determine the exact call relationships at compile or class load time.
- A static method
- The parent class method
- A constructor
- Private methods (cannot be overridden)
The above four classes of methods, called non-virtual methods, can convert symbolic references to direct references during class loading. That is, methods that can be called by Invokestatic and Invokespecial can be resolved statically.
- Invokestatic, for example
Static dispatch of a method
Let’s start with the code, which has to do with method overloading
public class StaticDispatchTest {
// Method overloading, which is a static behavior, is determined at compile time
public void test(Animal animal){
System.out.println("Animal");
}
public void test(Dog dog){
System.out.println("Dog");
}
public void test(Cat cat){
System.out.println("Cat");
}
public static void main(String[] args) {
StaticDispatchTest staticDispatch = new StaticDispatchTest();
Animal animalDog = new Dog();// The variable is declared Animal and the reference points to the Dog instance
Animal animalCat = new Cat();// The variable is declared Animal and the reference points to the Cat instance
staticDispatch.test(animalDog);// Call overloaded methods
staticDispatch.test(animalCat);// Call overloaded methods}}class Animal{}class Dog extends Animal{}class Cat extends Animal{}Copy the code
- The results of
instructions
Animal animalDog = new Dog();
Copy the code
- First, animalDog is declared as Animal, but it points to an instance of Dog. So the animalDog static type is the Animal class.
- It’s still Animal in nature, it’s static and it doesn’t change, even if you do a type conversion (Dog)animalDog, animalDog still doesn’t change. The conversion is generating something new, not converting the animalDog type in place.
- For method overloading, method arguments are based on the static type of the variable, regardless of the type of reference you point to.
- So the above code outputs Animal, even though the argument references Dog and Cat instances, respectively.
- In summary, we can conclude that the static type of a variable does not change, whereas the actual type of a variable can change (a manifestation of polymorphism), and the actual type is determined at runtime.
- You can also verify with bytecode to see which method is being called.
- If the code above is changed to
staticDispatch.test((Dog) animalDog);
staticDispatch.test(animalCat);
Copy the code
- The results of
- Now look at bytecode
Dynamic dispatch of methods
Let’s start with the code, which has to do with method rewriting
public class DynamicDispatchTest {
public static void main(String[] args) {
Fruit apple = new Apple();// Declare Fruit, reference to subclass
Fruit orange = new Orange();
apple.test();
orange.test();
apple = new Orange();// Modify the referenceapple.test(); }}class Fruit{
public void test(a){
System.out.println("Fruit"); }}class Apple extends Fruit{
@Override/ / rewrite
public void test(a){
System.out.println("Apple"); }}class Orange extends Fruit{
@Override/ / rewrite
public void test(a) {
System.out.println("Orange"); }}Copy the code
- The results of
- Now look at bytecode
- Dynamic dispatch of methods involves an important concept: method receivers.
invokevirtual
Polymorphic lookup flow of bytecode instructions:- At the top of the operand stack, find the actual type of the object, not the static type.
- It then goes to the constant pool to find matching names and descriptors, checks access permissions (ACC_PUBLIC, etc.), and returns a direct reference to the target method if everything fits and works.
- The symbol in the bytecode above refers to fruit.test, and a direct reference to each symbol reference can be determined at run time through the polymorphic lookup step of the Invokevirtual directive. To run exactly the corresponding method of the actual type.
- In short, you convert a symbolic reference to a direct reference.
Virtual method table
Every class has a virtual method table
- For the process of dynamic method invocation dispatch, the virtual machine creates a virtual method table (VTABLE) data structure in the method area of the class.
- For the InvokeInterface directive, the virtual machine creates a data structure called interface Method Table (itable).
The role of virtual method tables
- One step in the dynamic dispatch process is to find the corresponding method for the actual type. Virtual method tables are introduced for quick lookup purposes.
- After finding the object in the operand stack, go back to the virtual method table to find the method that was actually called.
- Finding a method in a virtual method table is a little tricky.
- If a subclass has not overridden a method of its parent class, it will look it up in the virtual method table of its parent class, which saves space.
- The same is true for Object, the top parent class, and if it’s not overridden, it’s going to look for the corresponding method in Object.
- Methods overridden by subclasses are generally the same as methods in their parent classes at the bytecode level. If the subclass finds test(), whose index is 5, and finds that it is not the method to call, but test() of its parent class, it will go directly to the place whose index is 5. (Improved search efficiency)
- The virtual method table is performed during the join phase of the class loading phase.
Make a few points
Understand static versus dynamic
- A syntactically incorrect code
class Animal{
public void test(a){}}class Dog extends Animal{
public void test(a){}public void testInDog(a){}}class Test{
public static void main(String[] args) {
Animal animalDog = newDog(); animalDog.testInDog(); }}Copy the code
- error
You might think that “Dog” says “testInDog()” and “new Dog()”.
Get explanations from bytecode instructions
summary
- Method calls have static and dynamic dispatch.
- Method calls in the static phase (compiled) take the declared static type, regardless of which instance object your symbolic reference refers to.
- To determine which class method is being called, a dynamic lookup is performed at run time.
- In order to find the corresponding method quickly, each class has a virtual method table to improve the efficiency of the search.