introduce

The Java virtual machine stack is thread-private and synchronizes with the declaration cycle of the thread. The virtual machine stack describes the memory model of Java method execution. Each method execution creates a stack frame containing local variables, operand stacks, dynamic connections, method exits, and so on.

Stack and the stack frame

Each method from execution to completion corresponds to the process of a stack frame in the virtual machine from stack to stack. The stack frame at the top of the Java virtual machine stack is the stack frame of the current execution method. The PC register points to that address. When this method call other methods for a long time will create a new stack frame, the new method of stack frames will be, the Java virtual machine stack stack into the current activity in the stack, only the current activity in the current stack local variables can be used, when the stack frame all instructions are complete, the stack frame is removed, before the frame becomes active stack, The return value of the previously removed stack frame becomes an operand of the stack frame.

The stack frame

A Stack Frame is a data structure used to support vm method invocation and method execution. It is a Stack element of the Virtual Machine Stack in the data area when the VM runs. The stack frame stores information about a method’s local variator, operand stack, dynamic linkage, and method return address. The first method corresponds to the process of a stack frame in the virtual machine stack from the beginning of the call to the completion of execution. Each stack frame contains a local variable table, operand stack, dynamic linkage, method return address, and some additional information. At the time of the compiled Code, how much local variables in the stack frame, how deep the operand stack are completely determined, and write Code attributes the method table, so a stack frame needs to how much memory allocation, not affected by the program run time variable data, but only depends on the specific implementation of the virtual machine. The chain of method calls in a thread can be long, with many methods handling execution state simultaneously. For the execution engine, only the Stack Frame at the top of the virtual machine Stack is valid in the 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 execution references operate only on the current stack frame. The conceptual structure of stack frame is shown in the figure below:

Local variable scale

  • The local variable table is a storage space for variable values. It is used to store method parameters and local variables defined within a method. When Java is compiled into a class file, the max_locals data item of the method’s Code property determines the maximum volume of local variables that the method needs to allocate.
  • A Slot in a 32-bit VM can store data types up to 32 bits (4 bytes) (Boolean, byte, CHAR, short, int, float, Reference, and returnAddress).
  • For 64-bit data types (long, double), the virtual machine allocates two contiguous Slot Spaces in high-aligned mode, splitting one long and double read and write into two 32-bit reads and writes.
  • The virtual machine specification does not specify the length of the Reference type, but in general, virtual machine implementations should at least be able to find, directly or indirectly, the starting address index of the object in the Java heap and object type data in the method area from this reference.
  • A Slot is reusable, and when variables in a Slot go out of scope, the next time a Slot is allocated, the original data will be overwritten. Slot references to objects affect GC (if they are referenced, they are not collected). Local variables are not initialized (both instance variables and class variables are initialized). That is, there is no preparation phase like class variables.
  • Local variables are not initialized (both instance variables and class variables are initialized). That is, there is no preparation phase like class variables.

The operand stack

  • The operand stack, like the local variable table, determines the maximum size of the local variable table to be allocated by this method at compile time.
  • Each element of the operand stack can be any Java data type, including long and double. 32-bit data types occupy 1 stack capacity and 64-bit data types occupy 2 stack capacity.
  • When a method is first executed, the operand stack of the method is empty. During the execution of the method, various bytecode instructions write and fetch contents to and from the operand stack. Arithmetic operations are performed through the operand stack, or arguments are passed through the operand stack when other methods are called).
  • In the conceptual model, stack frames should be independent of each other, but most virtual machines do some optimization to overlap local tables and operand stacks so that method calls can share parameters without having to do extra parameter copying. The overlapping process is shown in the figure:

Dynamic connection

Each stack frame contains a reference to the method in the constant pool of the runtime. The Class file has a large number of symbolic references in the constant pool, and the method invocation instructions in the bytecode take symbolic references to the methods in the constant pool as arguments. Some of these symbolic references are converted to direct references (static methods, private methods, etc.) during class loading or the first time they are used, which is called static resolution, and some are converted to direct references during each run, which is called dynamic joins. I won’t go into the process of parsing and dispatching for lack of space, but I just need to know the difference between static parsing and dynamic joining.

Method return address

When a method is executed, there are only two ways to exit the method:

  • The execution engine encounters a bytecode instruction returned by any method: passed to the upper-level method caller, the presence or absence of a return value and the type of return value are determined by which method is encountered to return the instruction. This exit method is called the normal completion exit.
  • Method execution encountered an exception: Regardless of whether they are produced inside the Java virtual machine abnormal or code thtrow exception, as long as there is no search in this method the exception table to match the exception handler, will cause the method exits, the exit way called abnormal complete export, if a method using the way out, is not to any return value of the upper caller. Whichever way you exit a method, you must return to where the method was called before the program can continue. When the method returns, it may store some information in the stack frame that is used to restore the execution state of the upper method. When a normal method exits, the value of the caller’s PC counter can be used as the return address, and it is likely that the frame stack will store the value of this counter as the return address. The process of method exit is the process of stack frame exit on the virtual machine stack, so the operation of exit may be: restore the local variable table and operand stack of the upper method, and push the return value into the operand stack of the caller. The value of each entire PC counter points to the next instruction that called the method.

Example: Java code:

public class Test {


    private static int add(int c){
        return c + 10;
    }


    public static void main(String[] args) {
        int a, b, c;
        a = 1;
        b = 2; c = add(a*b); c = c*(a+b); }}Copy the code

Decompiled using Javap -v

Classfile /E:/Code/JAVA/JUC/out/production/JUC/net/ziruo/juc/Test.class Last modified 2019-10-29; size 546 bytes MD5 checksum 3526f85e07771be800502f7e10b50a3a Compiled from "Test.java" public class net.ziruo.juc.Test minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #4.#24 // java/lang/Object."<init>":()V #2 = Methodref #3.#25 // net/ziruo/juc/Test.add:(I)I #3 = Class #26 // net/ziruo/juc/Test #4 = Class #27 // java/lang/Object #5 = Utf8 <init> #6 = Utf8 ()V #7 = Utf8 Code #8 = Utf8 LineNumberTable #9 = Utf8 LocalVariableTable #10 = Utf8 this #11 = Utf8 Lnet/ziruo/juc/Test; #12 = Utf8 add #13 = Utf8 (I)I #14 = Utf8 c #15 = Utf8 I #16 = Utf8 main #17 = Utf8 ([Ljava/lang/String; )V #18 = Utf8 args #19 = Utf8 [Ljava/lang/String;  #20 = Utf8 a #21 = Utf8 b #22 = Utf8 SourceFile #23 = Utf8 Test.java #24 = NameAndType #5:#6 // "<init>":()V #25 = NameAndType #12:#13 // add:(I)I #26 = Utf8 net/ziruo/juc/Test #27 = Utf8 java/lang/Object { public net.ziruo.juc.Test();  descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 8: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lnet/ziruo/juc/Test; private static int add(int); descriptor: (I)I flags: ACC_PRIVATE, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: iload_0 1: bipush 10 3: iadd 4: ireturn LineNumberTable: line 12: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 c I public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=4, args_size=1 0: iconst_1 1: istore_1 2: iconst_2 3: istore_2 4: iload_1 5: iload_2 6: imul 7: invokestatic #2 // Method add:(I)I 10: istore_3 11: iload_3 12: iload_1 13: iload_2 14: iadd 15: imul 16: istore_3 17: return LineNumberTable: line 18: 0 line 19: 2 line 20: 4 line 21: 11 line 22: 17 LocalVariableTable: Start Length Slot Name Signature 0 18 0 args [Ljava/lang/String; 2 16 1 a I 4 14 2 b I 11 7 3 c I }Copy the code

Main code annotation explanation:

0: iconst_1 (push 1 to the top of the operand stack) 1: istore_1 (push 2 to the top of the operand stack) 2: iconst_2 (push 2 to the top of the operand stack) 3: iconst_1 (push 1 to the top of the operand stack) 4: ilOAD_1 (ilOAD_1) 5: ILoAD_2 (iloAD_2) 6: iloAD_1 (ilOAD_2) Imul (multiply the operand at the top of the stack with the one below the top of the stack) 7: Invokestatic#2 // Method add (I)I (execute static Method, return value to operand stack)11: ILOAD_3: ilOAD_1: ILoAD_1: ILoAD_1: ILoAD_1: iloAD_1: iloAD_1: iloAD_1: iloAD_1: iloAD_1: iloAD_1: iloAD_1: iloAD_1: iloAD_1: iloAD_1: iloAD_1: iloAD_1 Iload_2 place the value of the local variable table index 2 at the top of the stack. 14: Iadd (add the value of the top and the value of the bottom of the stack) 15: IMul (multiply the value of the top and the value of the bottom of the stack) 16: Istore_3 (put the top of the operand stack into the local variable table index 3) 17:return(the end)Copy the code

Reference article: