How are classes loaded? How are and objects allocated and reclaimed? This section focuses on how methods are called and executed.
The stack frame
A stack frame is an element in the memory of a virtual machine stack and a data structure that supports method invocation and method execution by a virtual machine. It stores the method’s local variator table, operand stack, dynamic linkage, method return address, and some additional additional information. Each method call from the beginning to the end of execution, corresponding to the stack frame in the stack of the stack to the stack of the process.
The chain of method calls in a thread can be long, with many methods running at the same time. For the execution engine, only the stack frame at the top of the stack is valid in an active thread, called the current stack frame, and the method associated with this stack frame is called the current method. All bytecode instructions run by the execution engine operate only on the current stack frame.
The following is mainly introduced from three aspects: local variable table, dynamic join and operand stack.
Local variable scale
The local variable table is a space for storing variable values, which stores the familiar method parameters and local variables within the method. Its size is determined at compile time and written into the Code property of the method table. A Class file is a 2-byte stream of data arranged in a specific order. The method table is in the following position:
The Code attribute appears in the property set of the method table, but not all method tables have this attribute. Methods of interfaces and abstract classes, for example, do not have the Code attribute. Max_locals in the Code property defines the maximum amount of local locals that the method needs to allocate. The local variable table takes variable Slot as the smallest unit and stores data of the following data types:
The reference type refers to a reference to an instance of an object. Remember references to member variables of an object? Just one in stack memory and one in the local variable table code property of the stack frame.
Note that the Slot of the 0th index in the local variable table is used by default to pass a reference to the instance of the object that the method belongs to. The parameters are then assigned slots (starting at 1) in the order of the method arguments, and the remaining slots are finally assigned according to the order and scope of the variables defined in the method body.
Dynamic connection
As mentioned earlier, the Class file’s constant pool holds two main types of constants: literal constants and symbolic references. Symbolic references include fully qualified names of classes and interfaces, field names and descriptors, and method names and descriptors.
The target method in our method call is a symbolic reference in the constant pool in the Class file, and the method call instruction in the bytecode takes the symbolic reference to the method in the constant pool as an argument. Here’s an example:
10: invokevirtual #22 //Method ... A.hello:()V
Copy the code
Invokevirtual is a call instruction whose argument is the constant of item 22 in the constant pool. The comment shows that this constant is a symbolic reference to a.hoello ().
Java code does not “connect” when compiled by Javac, but rather “dynamically connect” when the virtual machine loads the Class file. That is, the Class file does not hold the memory entry addresses (direct references) for each method field, so the virtual machine cannot use it directly. This requires that symbolic references be retrieved from the virtual machine at runtime and resolved to direct references either at class creation time or at runtime.
In class loading phase, there will be a part of the symbol is directly reference is converted to direct references to this kind of transformation is called the static analysis, this kind of method conforms to the “immutable” compile time, run time, conform to the conditions of static method, a private method, instance constructor, the parent class method, the final approach, namely we often say the virtual method. The other part is converted to a direct reference during each run, called the dynamic join.
Each stack frame contains a symbolic reference to the method that the stack frame belongs to in the runtime constant pool, and this reference is held to support dynamic concatenation during method calls.
The operand stack
Operand stack, also called operation stack, is a last-in, first-out stack structure. The maximum depth of the operand stack is also written into the max_stacks data item in the Code attribute at compile time. Each data element of the operand stack can be any Java data type, including long and double, where the 32-bit data type is 1 and the 64-bit data type is 2.
Method invocation is not equal to method execution, the only task of method invocation stage is to determine which method to call, but also does not involve the specific operation process inside the method. During the execution of the method, various byte instructions are used to write and extract contents to the operation stack, that is, out/onto the stack operation. The bytecode instructions compiled by the compiler are stored in the Code property of the method property collection.
These instruction operations include pushing Slot data of a local variable table onto the top of the stack, pushing data off the stack and into Slot, or pushing data off the stack and back onto the stack by instruction, etc. Take the following method as an example:
public int a(a) {
int a = 10;
int b = 20;
return (a + b) * 100;
}
Copy the code
View the bytecode in javap -c as follows:
public int a(a);
Code:
0: bipush 10
2: istore_1
3: bipush 20
5: istore_2
6: iload_1
7: iload_2
8: iadd
9: bipush 100
11: imul
12: ireturn
Copy the code
When the method is executed, the action stack is empty.
The bipush instruction pushes the integer constant value of 10 to the top of the operand stack:
The istore_1 instruction removes the integer value from the top of the stack and stores it in the first local variable Slot. The next two instructions are the same, placing B :10 in local variable Slot.
Iload_1 copies the integer value in Slot 1 of the local variable table to the top of the stack:
In the same way, iloAD_1 copies the integer value in Slot 2 of the local variable table to the top of the stack when executing the instruction at offset 7:
The iadd command takes the first two operand elements off the stack, adds them together, and pushes the result back onto the stack. After iadd completes, elements 10 and 20 are pushed out of the stack, and the result 30 is pushed back:
:9 executes the instruction with offset address 9. The bipush instruction pushes the integer constant value 100 to the top of the operand stack:
:11 Executes the instruction with offset address 11. The imul instruction removes the top two elements from the stack, multiplies them, and pushes the result back onto the stack, just like the iadd operation:
:12 Executes the instruction at offset address 12, the iReturn instruction, which terminates the method execution and returns the integer value of the operation stack to the caller of the method. At this point, the method execution is complete.
Well, that concludes this article with an introduction to how methods are called and executed by the JVM. If this article is useful to you, give it a thumbs up. Everyone’s affirmation is also the motivation for Dumb I to keep writing.
1. Zhou Zhiming, Understanding JAVA Virtual Machines in Depth: China Machine Press