JVM learning notes - memory structure

catalogue

Overall architecture diagram

Program Counter Register

effect

characteristic

Java virtual machine stacks

definition

Problem Formulation

Stack memory overflow

Thread run diagnostics

Native Method Stack

definition

Heap

definition

characteristic

Heap memory overflow

Heap memory diagnostics

Method area

definition

Composition

Method area memory overflow

Runtime Constant Pool

StringTable

StringTable attribute

StringTable location

StringTable garbage collection

StringTable tuning

Direct memory

definition

benefit

Direct memory overflow

Distribution release principle

Overall architecture diagram

 

Program Counter Register

effect

Remember the execution address of the next JVM instruction (corresponding to the following number)

When the JVM executes 0, it puts address 3 into the program counter

The program counter is implemented using the registers of the operating system

The CPU cannot directly execute these binary bytecodes. These binary bytecodes need to be converted into machine codes through the interpreter before they can be executed by the CPU

0: getstatic #20 // PrintStream out = System.out; 
3: astore_1 // -- 
4: aload_1 // out.println(1); 
5: iconst_1 // -- 
6: invokevirtual #26 // -- 
9: aload_1 // out.println(2); 
10: iconst_2 // -- 
11: invokevirtual #26 // -- 
14: aload_1 // out.println(3); 
15: iconst_3 // -- 
16: invokevirtual #26 // -- 
19: aload_1 // out.println(4); 
20: iconst_4 // -- 
21: invokevirtual #26 // -- 
24: aload_1 // out.println(5); 
25: iconst_5 // -- 
26: invokevirtual #26 // -- 
29: return

characteristic

  • Thread private

  • There will be no memory overflow

Java virtual machine stacks

definition

  • The memory required for each thread to run is called the virtual machine stack

  • Each stack consists of multiple stack frames, corresponding to the memory occupied by each method call

  • Each thread can only have one active stack frame, corresponding to the method currently executing

Problem Formulation

  1. Does garbage collection involve stack memory?

    No, there is no garbage collection in the virtual machine stack.

  2. Is the larger the stack memory allocation, the better?

    No, it's better to use the default. The larger the stack memory allocation, it can only increase the number of recursive calls of this method (that is, increase the number of stack frames of this stack), which does not improve the execution efficiency of the method, but will reduce the number of multithreads.

  3. Are local variables in methods thread safe?

    If the local variable in the method does not escape the scope of the method, it is thread safe;

    If a local variable references an object and escapes the scope of a method, thread safety needs to be considered.

Stack memory overflow

  1. Excessive stack frames lead to stack memory overflow (recursion, using third-party class libraries)
public class Demo01 {

    private static int count;

    public static void main(String[] args) {
        method01();
    }

    private static void method01() {
        count++;
        method01();
    }

}

result:

    two    Stack memory overflow caused by too large stack frame

Thread run diagnostics

Case 1: excessive CPU usage

location

  1. top command to see which process is occupying too much CPU

  2. ps H -eo pid, tid (thread id),% cpu | grep the process number just found through top. Use the ps command to further check which thread occupies too much CPU

  3. Jstack process id is located by comparing the nid of the thread in the process with the tid just seen through the ps command. Note that the thread id found by jstack is hexadecimal and needs to be converted.

Native Method Stack

definition

Some methods with native keywords require java to call local C or C + + methods. Sometimes Java cannot directly interact with the underlying operating system, so it needs to use the local method stack to serve the methods with native keywords.

Heap

definition

Using the new keyword, heap memory is used when creating objects

characteristic

  • Thread sharing and heap objects need to consider thread safety

  • There is a garbage collection mechanism

Heap memory overflow

You can use - Xmx8m to specify the size of heap memory

give an example:

public class Demo01 {
    public static void main(String[] args) {
        int i = 0;
        List<String> list = new ArrayList<>();
        String str = "hello";
        while (true) {
            list.add(str);
            str = str + str;
        }
    }
}

result:

Heap memory diagnostics

jps tools

         Check which java processes are in the current system

jmap tool

         View heap memory usage jmap - heap process id

jconsole tool

         Graphical interface, multi-functional monitoring tool, which can monitor continuously

Visual JVM tools

Method area

definition

The Java virtual machine has a method area that is shared among all Java virtual machine threads. The method area is similar to the storage area of compiled code for traditional languages, or similar to the "text" section in the operating system process. It stores the structure of each class, such as runtime constant pool, field and method data, as well as the code of methods and constructors, including special methods for class and instance initialization and interface initialization. The method area is created when the virtual machine is started. Although the method area is logically part of the heap, a simple implementation may not choose to garbage collect or compress it. This specification does not enforce the location of the method area or the policy used to manage compiled code. The method area can have a fixed size, or can be expanded according to the needs of calculation, and can be shrunk if a larger method area is not required. The memory of the method area does not need to be continuous!

form

Method area memory overflow

When the program loads too many classes during running, it may lead to memory overflow in the method area

  • Permanent generation memory overflow before 1.8 (java.lang.OutOfMemoryError: PermGen space)

    • Use - XX:MaxPermSize=8m to specify the permanent generation memory size

  • Meta space memory overflow will occur after 1.8 (java.lang.OutOfMemoryError: Metaspace)

    • Use - XX:MaxMetaspaceSize=8m to specify the Metaspace size

Runtime Constant Pool

Binary bytecode includes: basic class information, constant pool, class method definition, and virtual machine instructions

Before learning about the runtime constant pool, let's learn about the constant pool through an example

public class Demo01 {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

After the above program is compiled, use the javap -v Demo01.class command on the console to decompile and view the bytecode file:

public class jvm.metaspace.Demo01
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#20         // java/lang/Object."<init>":()V
   #2 = Fieldref           #21.#22        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #23            // Hello World!
   #4 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #26            // jvm/metaspace/Demo01
   #6 = Class              #27            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Ljvm/metaspace/Demo01;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               Demo01.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = Class              #28            // java/lang/System
  #22 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;
  #23 = Utf8               Hello World!
  #24 = Class              #31            // java/io/PrintStream
  #25 = NameAndType        #32:#33        // println:(Ljava/lang/String;)V
  #26 = Utf8               jvm/metaspace/Demo01
  #27 = Utf8               java/lang/Object
  #28 = Utf8               java/lang/System
  #29 = Utf8               out
  #30 = Utf8               Ljava/io/PrintStream;
  #31 = Utf8               java/io/PrintStream
  #32 = Utf8               println
  #33 = Utf8               (Ljava/lang/String;)V
{
  public jvm.metaspace.Demo01();
    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
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Ljvm/metaspace/Demo01;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Hello World!
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 5: 0
        line 6: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}
SourceFile: "Demo01.java"

Constant pool: it is a table from which the virtual machine instruction finds the class name, method name, parameter type and literal information to be executed

Runtime constant pool: the constant pool is in the *. Class file. After the class is loaded, its constant pool information will be put into the runtime constant pool, and the symbolic address in it will be changed into the real address

StringTable

Program code

public class Demo01 {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "c";
    }
}

Part of the compiled binary bytecode file

Constant pool

Bytecode

  Local variable table

  analysis

The information in the constant pool will be loaded into the runtime constant pool. At this time, a, B and C are symbols in the constant pool and have not been changed into java string objects

ldc         # two         // Load it from the value "a" at position 2 in the constant pool. At this time, the symbol a becomes the string object "a" (put it into the string pool, do not put it if there is a string pool, and the string objects in the string pool are unique)

astore_ one         // Place "a" in the local variable table where the subscript is 1

ps: lazy loading is adopted, that is, this string object will be created only when this line of bytecode is run

Topic 1

public static void main(String[] args) {
    String s1 = "a";
    String s2 = "b";
    String s3 = "ab";
    String str = s1 + s2;
    System.out.println(s3 == str);
    // String s = "a" + "b" / / compile time optimization < = > "ab"
}

code analysis

  1. The string s3 is stored in the string pool.
  2. (S1 + S2) < = > (New stringbuilder(). Apend ("a"). Apend ("B"). Tostring(), this tostring() < = > new string ("ab")), so this str exists in the heap.
  3. Normally, new String() will first create a string object in the heap, and then go to the string pool to see if there is a string with the same content. If there is one, you don't need to create it. If not, a copy will also be stored in the string pool. However, the toString() method of StringBuilder is a little special. It will only be created in the heap, not in the string pool.

    Conclusion: the answer is: false, because one is in the string pool and the other is in the heap. It must be different.

Bytecode analysis

StringTable attribute

  • The string in the constant pool is only a symbol and becomes an object when it is used for the first time

  • The string pool mechanism is used to avoid repeated creation of string objects

  • The principle of string variable splicing is StringBuilder (1.8)

  • The principle of string constant splicing is compile time optimization

  • You can use the intern method to actively put string objects that are not in the string pool into the string pool

public class Demo01 {
    public static void main(String[] args) {
        String s1 = new String("a") + new String("b");
        String s2 = s1.intern();
        System.out.println(s1 == s2);   // JDK 1.7: true     JDK 1.6: false
    }
}

analysis:
    new String("a") + new String("b") A total of 6 objects are created as follows:
    (1) Create a in the heap StringBuilder object
    (2) Creates a string object in the heap"a"
    (3) Create a string object in the string pool"a"
    (4) call append Method will "a" Splice to StringBuilder in
    (5) Creates a string object in the heap"b"
    (6) Create a string object in the string pool"b"
    (7) call append Method will "a" Splice to StringBuilder in
    (8) call toString Methods the spliced "ab" Create in heap(new String("ab"))
    
    here s1 Points to the heap"ab"Address of(There are no strings in the pool"ab"String object),s1.intern()Is to put the"ab"Create a copy in the string pool. As above, if it exists, the reference address will be returned. If it does not exist, create a copy, and then return the reference address.
    It should be noted that in JDK 1.6 The string pool is still in the constant pool of the permanent generation s1.intern() Is to create objects directly in the string pool s1,And in JDK 1.7 After that, the string pool is placed in the heap, so s1.intern() Is stored in the heap in the string pool s1 Without creating an object.

StringTable location

In JDK 1.6, the string pool is in the constant pool of the permanent generation, while after JDK 1.7, the string pool is placed in the heap.

Reason: the recovery efficiency of the permanent generation is very low, which can only be triggered in FULLGC, while StringTable is used frequently. If the recovery efficiency is low, it is easy to lead to insufficient memory of the permanent generation.

StringTable garbage collection

-Xmx10m specifies the heap memory size

-20: + printstringtablestatistics print string constant pool information

-XX:+PrintGCDetails

-Verbose: the number of times gc prints, time-consuming and other information

StringTable tuning

  1. Adjust - XX:StringTableSize = number of barrels

If a large number of strings need to be stored in the program, the number of buckets (string pool is stored in hash table) can be modified according to the actual situation. Increasing the number of buckets can reduce hash conflict and improve string storage efficiency

  1. Consider pooling string objects

If a large number of strings need to be stored in the program, and many of these strings are repeated, these strings can be stored in the pool to reduce the occupation of memory

Direct memory

definition

Direct Memory

  • Common in NIO operations, it is used for data buffer

  • Allocation recovery cost is high, but read-write performance is high

  • Not managed by JVM memory reclamation

benefit

Before using direct memory:

analysis:

When the program reads the disk file information, it needs to divide a system cache in the system memory for storage, while the Java program cannot directly read this system cache, so it needs to divide a Java buffer (byte []) in the Java heap memory to read the information of the system cache. Only then can the Java program read the disk file information.

In this way, it is necessary to divide two memory areas (system buffer and Java buffer), resulting in unnecessary memory replication and low efficiency

After using direct memory, it is as follows:

analysis:

Direct memory is an area that can be accessed by both the operating system and Java code. There is no need to copy the code from the system memory to the Java heap memory, which improves the efficiency.

Direct memory overflow

public class Demo01 {
    public static int _100Mb = 1024 * 1024 * 100;
    public static void main(String[] args) {
        List<ByteBuffer> list = new ArrayList<>();
        int i = 0;
        try {
            while (true) {
                ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_100Mb);
                list.add(byteBuffer);
                i++;
            }
        } finally {
            System.out.println(i);
        }
    }
}

Operation results:

Distribution release principle

The allocation and release of direct memory is based on unsafe objects, not garbage collection

public class Demo01 {
    public static int _1GB = 1024 * 1024 * 1024;

    public static void main(String[] args) throws Exception {
        Unsafe unsafe = getUnsafe();
        // Allocate memory
        long base = unsafe.allocateMemory(_1GB);
        unsafe.setMemory(base, _1GB, (byte) 0);
        System.in.read();
        // Free memory
        unsafe.freeMemory(base);
        System.in.read();
    }

    /**
    * Get unsafe objects through reflection
    */
    public static Unsafe getUnsafe() throws Exception {
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        return (Unsafe) field.get(null);
    }
}

summary

  • The Unsafe class is used to complete the allocation and recycling of direct memory, and the freeMemory method needs to be actively called for recycling

  • The implementation of ByteBuffer internally uses Cleaner (Virtual Reference) to detect ByteBuffer. Once ByteBuffer is garbage collected, ReferenceHandler (daemon thread) will call the clean method of Cleaner and call freeMemory to free memory

-20: + disableexplicitgc / / disabled GC, that is, System.gc() is invalid

System.gc() executes full gc and takes a long time to recycle, so you can release direct memory by calling freeMemory through the unsafe object

Tags: Java jvm Back-end intellij-idea

Posted on Sun, 28 Nov 2021 01:25:08 -0500 by obrienkev