Interviewer: I don’t care about everything else. You must know this JVM virtual machine memory model

Preface

Before talking about the memory model of jvm, let's first understand the memory processing of the physical computer.
The interaction between the user's disk and the cpu on the physical calculator, because the read and write speed of the cpu is much faster than the read and write speed of the disk, so there is a memory (cache area). However, with the development of cpu, the read and write speed of memory cannot keep up with the read and write speed of cpu. The manufacturer of cpu adds a cache to each cpu, which is the following structure.

JVM composition analysis

  1. Runtime data area The
    runtime data area includes: stack, heap, method area (meta space), local method stack, and program counter. The detailed concept will be recorded later.
  2. The class loading subsystem loads
    the bytecode file into the runtime data area.
  3. Bytecode execution engine

Stack and stack frame

In Java, every time a thread is started, the virtual machine allocates a stack space and a program counter for it, and the stack space contains the stack frame corresponding to each method to be executed by this thread.
Let's first look at a simple code:

public class StackDemo {
	public static void main(String[] args) {
	        StackDemo sd = new StackDemo();
	        int number = sd.compute();
	        System.out.println("计算之后结果是:"+number);
	    }

	    public int compute(){
	        int a = 1;
	        int b = 2;
	        int c = (a + b) * 10;
	        return c;
	    }
}

Decompile the generated class file to generate the corresponding JVM instruction code:

Use the javap -c StackDemo.classcommand to decompile and decompile this class file, and output the instruction code directly to the console.

public class com.jdc.demo.StackDemo {
  public com.jdc.demo.StackDemo();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class com/jdc/demo/StackDemo
       3: dup
       4: invokespecial #3                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: invokevirtual #4                  // Method compute:()I
      12: istore_2
      13: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      16: new           #6                  // class java/lang/StringBuilder
      19: dup
      20: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
      23: ldc           #8                  // String 计算之后结果是:
      25: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      28: iload_2
      29: invokevirtual #10                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      32: invokevirtual #11                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      35: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      38: return

  public int compute();
    Code:
       0: iconst_1 //将int类型的常量1压入到操作数栈中。
       1: istore_1 //将int类型值 存入到局部变量1 这里指的是a
       2: iconst_2 //将int类型的常量2压入到操作数栈中。
       3: istore_2 //将int类型值 存入到局部变量2 这里指的是b
       4: iload_1 //从局部变量1中装载int类型值入栈。
       5: iload_2 //从局部变量2中装载int类型值入栈。
       6: iadd //将栈顶的两个int类型数相加,结果重新入栈。
       7: bipush        10 //往栈中压入10
       9: imul //将栈顶的两个int类型数相乘,结果重新入栈。
      10: istore_3 //将int类型值 存入到局部变量3 这里指的是c
      11: iload_3 //从局部变量3(c)中装载int类型值入栈。(是为了return)
      12: ireturn //返回
}

It is not difficult to see that this paragraph is the simple StackDemo class we just wrote, which can be parsed by checking the script provided by Oracle. There are also many online. Here I have marked the meaning of the instructions used in the compute() method in the comments.
Take a look at the execution process in the comments of the compute() method.

Let's look at the execution process in conjunction with the diagram:

    栈的数据结构:FILO
   上述图中main线程中有两个方法,main()方法和一个负责计算数字的compute()方法,具体执行过程如下:

  1. After the main thread is started, the main() method is executed first, and a new space (stack frame) is opened up in the stack space for it when the main() method is executed.
  2. The creation of local variables inside the main() method is stored in this stack frame. But it should be noted that if the local variable here is an object, its value is not stored in the local variable table, but stored in the heap. Here it points to the corresponding address in the heap. The figure 1 in the previous section can be It can be seen that there are many object references to the heap in the stack.
  3. When the main() method is executed to the step where the compute() method is called, the thread invokes the compute() method. At this time, the compute() method is pushed onto the stack and also allocates a stack frame to store its own local variables.
  4. Wait for the compute() method to complete its logic, then exit the entire stack, and the compute() method will pop out of the stack
  5. At this time, return and continue to perform the following operations in the main() method.

Local variable table

As can be seen in the figure above and during execution, the local variable table and operand stack cooperate to complete the data processing operation.
For example, int a=1,
there are three steps in the instruction code:

  • Put this 1 into the operand stack first,
  • At the same time, apply a small place to store the variable a in the local variable table.
  • Then pop 1 from the operand and assign it to a in the local variable table to complete the assignment operation.

Operand stack

As can be seen in the figure in the stack section and during execution, the local variable table and operand stack cooperate to complete the data processing operation.
For example, int a=1,
there are three steps in the instruction code:

  • Put this 1 into the operand stack first,
  • At the same time, apply a small place to store the variable a in the local variable table.
  • Then pop 1 from the operand and assign it to a in the local variable table to complete the assignment operation.

Dynamic link

Dynamic linking: The method is stored in the method area, and the method is loaded into the corresponding entry memory address of the method area (when other methods are called) through dynamic linking, you can easily know the address of the corresponding method code in the method area.

Method export

After the compute() method is executed, return to the main() method. At this time, continue to execute the next sentence of the compute() method in the main() method, instead of restarting the execution from the first sentence of the main() method. This is the method export.

Program counter

The program counter is a very simple but very important design.

Each thread starts with a program counter, as in the jvm instruction code generated in the above and stack frame chapters, there are numbers 0 1 2... on the far left, and this value is for the program counter.

Why design a program counter?

Program counter function: Because Java is multi-threaded execution, there are thread priorities. If during the execution of this thread, a thread with a higher priority comes to grab CPU resources, and the thread with higher priority executes After completion, the CPU resources are returned to the current thread, and the current thread knows which step it is currently executing through the program counter.

Heap memory

Heap memory is the most important and complex part. It is not only responsible for creating new objects, but also for gc. One of the important indicators for judging a system performance is the programmer's management of heap memory. Because most JVM tuning will mention heap memory.

First introduce the composition of heap memory:

1. The created objects will be placed in the Eden area of ​​the young generation. When the objects in the Eden area are full, the virtual machine will perform gc at this time, but the gc here is not full gc, but minor gc, which is to clean up only the objects of the young generation. Regardless of the objects in the elderly area, at this time, we must mention the GCRoots root node (local variables in the thread stack, static variables, variables in the local method stack, etc.). Before gc is needed, jvm will follow each GCRoots in Eden The root node looks for the references under it, looking down layer by layer until the last object has no other references. At this time, the virtual machine treats all objects in the entire process as non-garbage objects.

2. In gc, these non-garbage objects will be assigned to the S1 area, and then the remaining unreferenced garbage objects in the Eden area will be cleaned up. After the cleanup is completed, the Eden area is vacated, and the useful objects are now stored in S1 , And then replace S1 and S2 (previously, Eden and S1 are matched, and the next gc is Eden and S2). When performing gc for the second time, the same operation as the first gc is performed on the Eden area and the S1 area just now... After each gc, the age of the surviving object will be +1, after a certain number of gc, that is Say that this survivor object is old enough, and then the virtual machine will put it into the old age. ------->Use java's own tools to view jvisualvm

3. When the old area is full, the jvm will perform a very time-consuming full gc. At this time, the program can't continue. When the full gc is finished, if it goes well, the program will continue to execute, but there is some performance Loss, because the jvm tuning that is often said to be plain is to reduce the number of GCs and reduce the time of each GC (you can set the initial heap size... etc.), if the objects in the old age are still non-garbage objects, then it will OOM memory overflow occurred.

Here to mention the garbage collection algorithm of JVM

There are four kinds of garbage collection algorithms. Let me introduce them one by one:
1. Mark-clear: The most basic and simplest algorithm is the easiest to implement. It is divided into two stages. The first stage marks objects that are no longer referenced (garbage). , The second stage is cleared.
Advantages: convenient.
Disadvantages: memory fragmentation occurs, because you don't know where the garbage and non-garbage are on a piece of memory, so there will be a lot of memory fragments after cleaning up.

2. Copy algorithm: It is the method still used by the new generation, but it is optimized (8:1:1) when used now. Its implementation is to divide a piece of memory into two pieces of the same size, and use one piece each time. When this piece of memory is full, move the surviving objects on it to another piece of memory. . . recycle.
Disadvantages: Insufficient memory usage, spent 100 yuan, but only 50 yuan of service.

3. Marking-sorting: This algorithm is similar to the first type of marking removal. It marks garbage objects first in the first stage, but the second phase of marking sorting is not to clean up immediately, but to move the surviving objects to one side. , And finally clean up the memory on the garbage object side.

4. Generational collection: JVM is currently the most used, probably using the replication algorithm for the young generation, while the labeling and sorting algorithm is used for the elderly. Because the new generation produces objects all the time, it is very easy to fill up, which means that it needs to be cleaned frequently, so the copy algorithm is adopted, but its copy algorithm is not about dividing the memory into two parts of 1:1, but The default is 8:1:1

You can look at the changes in memory through the jvisualvm that comes with java

Only one picture is attached here, because the monitoring on it is dynamically changing, and theoretically it has also been recorded and cleared. Interested friends can try it by themselves.
Code:

public class Test{

    public static void main(String[] args) throws Exception {
        List<Test> list = new ArrayList<Test>();
        //死循环,让一直创建对象,并且都是有引用对象
        while (true){
            list.add(new Test());
            Thread.sleep(10);
        }
    }
}

win+R—>cmd—>jvisualvm
上图:

Mention about jvm tuning

STW: stop the word means that during gc, the program thread is suspended. At this time, it depends on the amount of garbage. If there is too much, gc will take a long time to execute, and the user experience will be very poor at this time.
Jvm tuning: reduce the number and time of STW, but STW is a must, this is the design mechanism of java. Like the program counter, it is very clever.

Reason: As you can see from the first picture of this article, take the stack and the heap as an example. The methods in the stack have references to objects, and this reference points to the heap. When gc is used, this GCRoots root takes one step. Find the heap in one step, and then find the next reference from the heap. For example, in a project, the object is constantly being created (the first few seconds of activities and promotions in an e-commerce project). At this time, it is created in one second The object may be tens of MB, plus a series of related order object creation, shopping cart, and so on. If I gc once at this time, gc takes time, and its time is the same as STW time, but if there is no STW, I have thousands of objects in the process of creating before the start of gc (this time They have references, such as the order object created depends on the shopping cart object). At this time, the gc is completed, and the method stack frame in the thread stack will be released. After the stack frame is released, there is naturally no local variable table of the method. And because I started by using an object in the local variable table as the GCRoots root to look for references. If there is no STW time, my object is not garbage after the execution of gc, but its current GCRoots root is gone. Naturally, there is no reference. At this time, my object becomes garbage, and it becomes garbage if it is not used. Then the program will definitely be GG. . .

Method area

The main storage is constants, static variables and class information.

Native method stack

Method of executing non-java native code (native keyword).

At last

Thank you for seeing here. Please correct me if there are any shortcomings in the article. If you think the article is helpful to you, remember to give me a thumbs up. You will share java-related technical articles or industry information every day. Welcome everyone to follow and forward the article!

Guess you like

Origin blog.csdn.net/weixin_47277170/article/details/108381702