JVM -- memory model

When senior Android engineers want to do performance optimization, NDK, soundness of design architecture, they must have a certain understanding of JVM. The more technology goes up, the more we need to understand the underlying. Computer principle, c/c + + language, JVM principle, data structure and algorithm are indispensable. In the process of learning, we feel that knowledge is linear at first, just like an array. We only need to constantly fill it with data. However, the more we learn later, the more we find that knowledge is broad. When we reach a certain level, we often need to learn a series of other knowledge, many of which need to use other programming languages, I gradually understand the origin of the whole stack of engineers. There is no end to the road of technology, only continuous learning
The content of the JVM tends to be conceptual. It's not like you can see the results after code execution. It's also troublesome to summarize. It's better to read the books related to the JVM if conditions permit. You can also discuss the places where the summary is not in place

1, JVM memory model

Java is an interpreted language. First, it needs to be compiled into a class file and then loaded by the JVM. Finally, the JVM will interpret it into machine code that the system can run directly. In terms of performance, it is certainly not as good as directly compiling into machine code, so why doesn't Java directly compile into machine code? Because different operating systems have different rules for identifying machine codes, and we only write one copy of code. The JVM is responsible for escaping into machine codes under different operating systems. The benefits are: compile once and run everywhere

The above figure shows the memory model in which the JVM loads class es into memory. In addition to cross platform, the benefits brought by the JVM also include automatic memory allocation, which is also the focus we need to understand. When c/c + + programmers want to use a large amount of data, they need to dynamically apply for memory and manually release the memory at an appropriate time. Once they forget to release, resulting in wild pointers, memory leakage will occur. The JVM helps us free from the tedious work of applying for memory and freeing memory

2, Thread sharing and private

When the JVM runs the program, it is divided into two data areas, a shared data area and a private data area

1. Shared data area

Imagine which of the following Java code is fixed? What code is dynamic? Fixed can be expressed in text, such as class name, properties in class, constants, method name, code execution order, etc. It's actually the code we wrote Dynamic is something that cannot be represented by text, such as the value obtained by a series of operations on a variable. In fact, it is an operation that requires cpu intervention

Next, look at a piece of code:

public class Hello {

    public int test() {
        int a = 3;
        int b = 4;

        return a + b;
    }

    public static void main(String[] args) {
        Hello h = new Hello();
        h.test();
    }

}
1.1 method area

After compiling with javac, we get the class file. If we run the program and execute the main function, in which the hello object is instantiated, the JVM will load the class file and store it in the method area (meta space)

Hello.class loading.png

At this time, the class file has been escaped. We can use the javap command to decompile to see what the above code looks like in the JVM

javap -v Hello.class

There is no need to take a closer look. The test method will be described in detail later

Classfile /C:/Users/tyqhc/Documents/javaworkspace/myJava/out/production/myJava/Hello.class
  Last modified 2021-9-27; size 525 bytes
  MD5 checksum 74b6dc87932a59d4af95208b0eb1edb7
  Compiled from "Hello.java"
public class Hello
  SourceFile: "Hello.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#25         //  java/lang/Object."<init>":()V
   #2 = Class              #26            //  Hello
   #3 = Methodref          #2.#25         //  Hello."<init>":()V
   #4 = Methodref          #2.#27         //  Hello.test:()I
   #5 = Class              #28            //  java/lang/Object
   #6 = Utf8               <init>
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               LocalVariableTable
  #11 = Utf8               this
  #12 = Utf8               LHello;
  #13 = Utf8               test
  #14 = Utf8               ()I
  #15 = Utf8               a
  #16 = Utf8               I
  #17 = Utf8               b
  #18 = Utf8               main
  #19 = Utf8               ([Ljava/lang/String;)V
  #20 = Utf8               args
  #21 = Utf8               [Ljava/lang/String;
  #22 = Utf8               h
  #23 = Utf8               SourceFile
  #24 = Utf8               Hello.java
  #25 = NameAndType        #6:#7          //  "<init>":()V
  #26 = Utf8               Hello
  #27 = NameAndType        #13:#14        //  test:()I
  #28 = Utf8               java/lang/Object
{
  public Hello();
    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 1: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       5     0  this   LHello;

  public int test();
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: iconst_3
         1: istore_1
         2: iconst_4
         3: istore_2
         4: iload_1
         5: iload_2
         6: iadd
         7: ireturn
      LineNumberTable:
        line 4: 0
        line 5: 2
        line 7: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       8     0  this   LHello;
               2       6     1     a   I
               4       4     2     b   I

  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #2                  // class Hello
         3: dup
         4: invokespecial #3                  // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: invokevirtual #4                  // Method test:()I
        12: pop
        13: return
      LineNumberTable:
        line 11: 0
        line 12: 8
        line 13: 13
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0      14     0  args   [Ljava/lang/String;
               8       6     1     h   LHello;
}

Each class in Java (we won't discuss internal classes) corresponds to a class file. The information of the class file (class meta information) is stored in the method area, which is a shared data area, and only one copy of the same class loader will be loaded. When we need to instantiate the object of this class, we will find it from the method area. If it has been loaded before, we can use it directly. Anyone can instantiate this class. Naturally, it is stored in the shared data area

1.2 reactor

The method area stores class information, and the heap stores instantiated objects. Objects of the same class can be instantiated multiple times. Objects can be used by other threads, so the heap is also a shared data area. When instantiating an object, there is an object header in the object, in which a type pointer will point to the class meta information in the method area. Just take a look at the figure below. There is no need to delve into it

Object header composition.png

2. Private data area

In addition to the method area and heap, all other data areas are private data areas. As mentioned earlier, private data areas are data of method runtime and require cpu intervention

2.1 stack frame

Java stack is also called virtual machine stack and thread stack. It doesn't matter what it is called. What matters is the content: stack frame

The stack frame is mainly composed of four parts:

  • Local variable table: stores local variables and their values
  • Operand stack: stores temporary data during operation
  • Dynamic links: polymorphic correlation
  • Method exit: enter the stack frame of the next method

When each method is called, a new stack frame will be created, and then the code in the method will be executed. The assembly instruction in the test method above is:

         0: iconst_3
         1: istore_1
         2: iconst_4
         3: istore_2
         4: iload_1
         5: iload_2
         6: iadd
         7: ireturn

A gif diagram is used to show how the test method operates in the stack frame:

2.2 program counter and local method stack

Program counter: the program counter is to temporarily record the line to which the method runs. In fact, there is no parallelism in the program running, but different threads constantly seize the cpu, execute for a period of time, and then start the competition again. However, the execution time is too short, so people can't feel it at all. When a method runs to a certain line, it starts to compete again, You need to record where the current method is running, so that the next time the cpu is preempted by the method, it can continue to execute from the place where it was interrupted last time

Local method stack: the local method stack is used to run c/c + + code. Its structure is the same as that of Java stack. The difference is that the memory dynamically applied by c/c + + code is manually managed by itself

For the memory model, just understand it. For mobile terminal development, we do memory optimization for the memory in the heap. The next article will introduce the memory structure in the heap

Posted on Mon, 06 Dec 2021 21:10:03 -0500 by the_crazyman