Runtime data area

The Java runtime data area is divided into the following sections: program counter, Java virtual machine stack, local method stack, Java heap, and method area. The program counter, Java virtual machine stack, and local method stack are thread-private, meaning that each thread has these parts. The Java heap and method areas are shared by threads, so let’s take a look at each of these areas.

Program counter:

The program counter is a line number indicator of the bytecode being executed by the current thread, and the bytecode interpreter works by changing the value of this counter to select the next bytecode instruction to be executed. Basic functions such as branching, looping, jumping, exception handling and thread recovery rely on program counters. Each thread has a separate program counter.

Java Virtual Machine stack:

The Java virtual machine stack is also thread-private and has the same life cycle as a thread. The virtual machine stack describes the in-memory model of Java method execution. Each method execution creates a stack frame that stores information about local variables, operand stacks, dynamic links, method exits, and so on. The process from the method invocation to the completion of execution corresponds to the process of a stack frame in and out of the virtual machine stack.

Local method stack:

The vm stack is similar to the virtual machine stack, except that the virtual machine stack executes Java methods (that is, bytecode) for the VIRTUAL machine, while the local method stack serves Native methods used by the virtual machine.

The Java heap:

The Java heap is the largest chunk of memory managed by the Java Virtual machine and is shared by all threads. The sole purpose of this memory area is to hold object instances.

Methods area:

The method area, like the Java heap, is shared by threads and is used to store class information, constants, and static variables that have been loaded by the virtual machine.

Structure of the virtual machine stack

The virtual machine stack describes the in-memory model of Java method execution. This section focuses on using ASM modification methods, so let’s take a look at the virtual stack structure as shown below:

Local variation scale:

Inside each stack frame is a list of variables called a local variable table. The length of the local variable table in the stack frame is determined at compile time and is saved and provided to the stack frame through the Code property of the method. One local variable can hold one data of type Boolean, byte, CHAR, short, float, Reference, and returnAddress, and two local variables can hold one data of type long and double. Local variables use an index for location access. The first local variable has an index value of zero.

The Java virtual machine (JVM) uses a local variable table to pass arguments to method calls. When a method is called, its arguments are passed to a continuous local variable table starting at 0. In particular, when an instance method is called, the 0th local variable must store a reference to the object on which the instance method is called (that is, the “this” keyword in the Java language). Subsequent parameters will be passed to a continuous local variable table position starting at 1.

Operand stack:

The length of the operand stack in a stack frame is determined by compile time. The operand stack is empty when the frame is created. The Java virtual machine provides bytecode instructions for copying constant or variable values from the fields of a local variable table or object instance to the operand stack. It also provides instructions for fetching data from the operand stack, manipulating data, and pushing the results of operations back onto the stack. At the time of a method call, the operand stack is also used to prepare the parameters of the calling method and to receive the results of the method return.

Dynamic linking:

In a Class file, describing a method that calls another method or accessing its member variables is represented by symbolic references. Dynamic linking converts these symbolic references to direct references to the actual method.

Return address:

When a method is executed, either the method call completes normally or the method exception call completes. Either way, after the method exits, you need to return to where the method was called.

The method invocation link in a thread can be very long and many methods are executing simultaneously. For the execution engine, the stack frame at the top of the current stack is valid in an active thread, called the current frame, and the method associated with this stack frame is called the current method.

Virtual machine stack running process

Let’s take another look at how stack frames work with an example.

First, write classFileAnalysis.java source code as follows:

package com.asm.demo;
public class ClassFileAnalysis {
    public static int add(int i, int j) {
        int result = i + j;
        returnresult; }}Copy the code
  • Execute javac classFileAnalysis.java to generate classFileAnalysis.class

  • Decompile javap -verbose classFileanalysis.class

-verbose Indicates the number of print method parameters, local variables, and stack size.

Classfile /Users/yangpeng/Desktop/ASM_METHOD/ClassFileAnalysis.class
  Last modified Mar 5, 2019; size 283 bytes
  MD5 checksum 8982796d91e9cd9cdebccf4c26c8ed52
  Compiled from "ClassFileAnalysis.java"
public class com.asm.demo.ClassFileAnalysis
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#12         // java/lang/Object."<init>":()V
   #2 = Class              #13            // com/asm/demo/ClassFileAnalysis
   #3 = Class              #14            // java/lang/Object
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               add
   #9 = Utf8               (II)I
  #10 = Utf8               SourceFile
  #11 = Utf8               ClassFileAnalysis.java
  #12 = NameAndType        #4:#5          // "<init>":()V
  #13 = Utf8               com/asm/demo/ClassFileAnalysis
  #14 = Utf8               java/lang/Object
{
  public com.asm.demo.ClassFileAnalysis();
    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 3: 0

  public static int add(int, int);
    descriptor: (II)I
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=2
         0: iload_0
         1: iload_1
         2: iadd
         3: istore_2
         4: iload_2
         5: ireturn
      LineNumberTable:
        line 6: 0
        line 7: 4
}
SourceFile: "ClassFileAnalysis.java"
Copy the code

You can see complete information about the class, such as class name, version number, constant pool method information, and so on. Since we are analyzing the use of ASM to manipulate methods, we will focus on the Code part of the Add method, which is as follows:

You can see that the add method’s local table size is 3, operand stack size is 2, and the number of parameters is 2. Six instructions were executed. The meaning of each instruction is as follows:

  • Iload_0: pushes the first local variable of type int to the top of the stack
  • Iload_1: Pushes the second local variable of type int to the top of the stack
  • Iadd: Pushes the top two elements off the stack, adds them together, and pushes the result to the top
  • Istore_2: Stores a value of type int into the third local variable
  • Iload_2: Pushes the third local variable of type int to the top of the stack
  • Ireturn: Returns an int from the current method

More instructions please refer to the Java virtual machine specification docs.oracle.com/javase/spec…

Let’s look at how the local table and operand stack change if we call the add method and pass in two and three parameters to calculate the sum of two and three.

In the initial state, the local variable table has two arguments we passed in, 2 and 3, and the operand stack is empty.

Execute iload_0 execute iload_0 and then 2 is pushed onto the stack

Execute iloAD_1 execute iloAD_1, and then stack 3

Execute iadd execute iadd, remove 2 and 3 from the stack, add them together, and add 5 to the stack

Run istore_2. After istore_2 is executed, 5 is removed from the stack and added to the local variable table

Iload_2 After iload_2 is executed, 5 is added to the stack

Executing iReturn After executing iReturn, 5 is returned as the result

After the above steps, the method is called and we calculate the value of 2+3.