The data structure within a stack frame

  • Local Variables: Record the this pointer, method parameters, and Local Variables of non-static methods

  • Operand Stack: Stack structure used for computation

  • Dynamic Link: Method references to the runtime constant pool

  • Return Address: Definition of normal or abnormal exit of a method, and Return value passing between methods. (Note: in Java, methods can also exit because of exceptions, so the Return Address of a method is not necessarily the Address at which the method exits.)

  • Additional information

Local variable scale

Each frame contains an array of variables known as its local variables. The length of the local variable array of a frame is determined at compile-time and supplied in the binary representation of a class or interface along with the code for the method associated with the frame

The main storage method parameters, local variables in the method, data types including basic data type, object reference (numeric address), return address. The local variable scale has the following characteristics:

  • The size of the local variable table array is determined at compile time and does not change later
  • The local variable table is per-thread private and there are no thread-safety issues

Local variable scale for nonstatic methods

  • The constructor, instance method, comes with a this reference variable
  • Static methods do not have a reference to this in their local variable table

Variable Slot

The most basic storage unit in the local variable table. Each slot size is 32 bits

  • The local variable table is an array starting with index0, and each element of the array is called a variable slot
  • In a local variable table, types up to 32 bits occupy one slot (int), 64-bit two slots (long, double), and reference types occupy one slot
  • Call a variable that occupies two slots using the initial index

Slot reuse

  • The slots of local variables in a stack frame can be reused. If a local variable’s scope has expired, its slots can be used by local variables declared after it

  • This design makes the space utilization rate of the local variable scale higher and does not cause vacancy.

    Examples of reuse:

    public void test1(a){
    	int a = 0;
    	{
    		int b = 0;
    		b = a + 1;
    	}
    	int c = 1;
    }
    Copy the code

The local variation table of the above code is shown in the figure. It can be seen that the slots of B and C are index=2, which means that after the scope of B ends, C will reuse the slot of B.

Local variable meter and garbage collection

  • Variables in the local variable scale are important garbage collection nodes. Only objects referenced directly or indirectly in the local variable scale will not be collected

Operand Stack

Each frame contains a last-in-first-out (LIFO) stack known as its operand stack. The maximum depth of the operand stack of a frame is determined at compile-time and is supplied along with the code for the method associated with the frame

Operand stack, in which data is written to or extracted from the stack according to bytecode instructions during method execution

  • Some bytecode instructions push values into the operand stack, while others can pull out values and push them back after use
  • For example: copy operation, swap, sum
  • Bytecode instructions are translated by the execution engine into machine instructions
  • An operand stack is a structure in a stack frame that is created at the beginning of the execution of a method
  • Each stack of operands has a fixed depth determined at compile time, which is stored in the max_stack value of the code attribute
  • 32 bits account for one stack depth and 64 bits for two stack depths

I ++ is the same thing as ++ I

Scenario 1

public void test(a){
       int i1 = 10;
       i1++;

       int i2 = 20;
       ++i2;
   }		
Copy the code

The bytecode compiled by the above code is as shown in the figure above. It can be seen that the bytecode compiled by I ++ and ++ I are actually the same. First, bipush pushes the initial value of I into the operand stack, and then istore is used to exit the stack and store the local variable table. Then the iINC instruction is used to take out the data in the local variable table and +1

Scenario 2

public void test(a){
       int i1 = 10;
       int d1 = i1++;

       int i2 = 20;
       int d2 = ++i2;
   }
Copy the code

After compiling the above code, you can see that the bytecode assigned by I ++ and ++ I is different.

Int d = i++ : bipush the initial value into the operand stack, istore the initial value into the local variable table. When assigning a value to D, iloAD instruction is first used to read I of the local variable table to the operand stack (temporarily in the stack, not out of the stack), and then iINC instruction is used to add 1 to I of the local variable table. After completion, ISTore instruction is used to remove I that is not added in the operand stack and store it in a new slot of the local variable table.

Int d = ++ I: the only difference is that iINC is used to add I to the local variable table, iloAD is used to add I to the operand stack, and istore is used to assign to the local variable

Situation 3 (Crazy)

public void test(a){
       int i1 = 10;

       i1 = i1++;

       int i2 = 10;
       i2 = ++i2;
   }
Copy the code

The bytecode of the above code is shown above

I = I ++ case: bipush was used to push 10 onto the operand stack, istore was used to push the initial value 10 off the stack and into the local variable table, and iloAD was used to push the local variable 10 onto the operand stack (the value did not change in subsequent operations). Then iINC instruction was used to remove, stack, add one, remove the local variable I and store it in the local variable table. This process does not change the initial value of 10 that was originally pushed. Finally, the istore directive is used to override the 10 over the local variable that was previously incremented by one. So in this case the value of I is the same.

I = ++ I Situation: Similarly, the initial value of 10 is stored in the local variable table with bipush and ISTore. Since ++ is before I, iINC instruction is used to change 10+1 in the local variable table to 11, and then ILoAD and istore instructions are used to assign the value to I. Finally, I will become 11

In fact, through the analysis of the above two cases, we can find that the last ILoAD and ISTore instructions in the second case are actually of no practical significance. They just put I on and off the stack of operands and store it in the original local variable table position.

Dynamic Linking

Each frame contains a reference to the run-time constant pool for the type of the current method to support dynamic linking of the method code

A reference contained in each stack frame to the method that the stack frame belongs to in the runtime constant pool

  • A method reference to the runtime constant pool
  • The purpose of dynamic linking is to convert symbolic references to direct references