JVM -- JVM memory structure: program counter, virtual machine stack, local method stack, heap, method area (2)

Reference before reading

https://blog.csdn.net/MinggeQingchun/article/details/126947384

The memory structure of the JVM is roughly divided into five parts, namely the program counter, virtual machine stack, local method stack, heap, and method area . In addition to this, there is direct memory outside the JVM that is referenced by the heap

JVM8 official website document address

The Java® Virtual Machine Specification

JVM8 official website Options parameter configuration document

 https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

 Java HotSpot VM Options

JDK1.8 official website address 

https://docs.oracle.com/javase/8/docs/index.html

JDK1.6 official website address

https://docs.oracle.com/javase/6/docs/index.html

JDK = JRE + development tool set (such as Javac compilation tools, etc.)
JRE = JVM + Java SE standard class library

1. Program Counter Program Counter Register (register)

Function: remember the execution address of the next jvm instruction

Features:

(1) Thread private

(2) There will be no memory overflow

The program counter (Program Counter Register) is a small memory area in the JVM, which stores the memory address of the virtual machine bytecode instruction executed by the current thread (it can be regarded as the line number indicator of the bytecode executed by the current thread )

The multi-threading of the JVM is realized by switching threads in turn and allocating CPU execution time slices. At any one time, a CPU will only execute instructions in one thread . In order to ensure that the correct execution position can be restored after thread switching, each thread needs to have an independent program counter, and the program counters between threads are stored independently without affecting each other.

This area is the only area that does not specify any OutOfMemoryError in the java virtual machine specification, because the program counter is maintained internally by the virtual machine and does not require developers to operate 

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

The interpreter will interpret the instruction as machine code and give it to the CPU for execution, and the program counter will record the address line number of the next instruction, so that the next time the interpreter will get the instruction from the program counter and then interpret and execute it

In a multi-threaded environment, if a context switch occurs between two threads, the program counter will record the address line number of the next line of instruction in the thread, so that it can be executed next 

Second, the virtual machine stack  Java Virtual Machine Stacks

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

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

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

 The virtual machine stack (Java Virtual Machine Stacks) is thread-isolated. Every time a thread is created, a corresponding Java stack is created, that is, each thread has its own independent virtual machine stack.

This stack will also contain multiple stack frames. Every time a method is called, a stack frame will be created and pushed into the stack . The stack frame stores information such as local variable table, operation stack, dynamic link, and method exit . Each The process of a method from calling to finally returning the result corresponds to the process of a stack frame from being pushed to being popped

The virtual machine stack is a last-in-first-out data structure. During thread running, only the stack frame at the top of the stack is valid, which is called the current stack frame . The method associated with this stack frame is called the current method. The active frame stack is always the top element of the virtual machine stack

The local variable table stores various basic data types and object reference types known at compile time. Usually what we call " stack memory " refers to the part of the local variable table.

The memory space required by the local variable table is allocated during compilation. When entering a method, how much memory the method needs to allocate in the frame is fixed, and the size of the local variable table will not be changed during operation.

64-bit long and double type data will occupy 2 local variable spaces, and other data types only occupy 1 space

As follows, the method call generates 3 stack frames 

  

Stack memory does not involve garbage collection. The generation of stack memory is the stack frame memory generated by method calls one by one, and the stack frame memory will be popped from the stack every time the method is called, and will be automatically recycled without garbage collection management. 

1. Stack  OverflowError

1. In the case of a fixed size, the JVM will allocate a certain memory size (-Xss parameter) for the virtual machine stack of each thread, so the number of stack frames that the virtual machine stack can hold is limited. If the stack frames are continuously pushed into the stack Without popping the stack, the memory space of the virtual machine stack of the current thread will eventually be exhausted, and a StackOverflowError exception will be thrown ( too many stack frames, too large will cause stack memory overflow )

2. In the case of dynamic expansion, when the memory of the entire virtual machine stack is exhausted and no new memory can be applied for, an OutOfMemoryError exception  will be thrown

1. Recursively call the method itself

Run the following code

/**
 * 栈内存溢出 java.lang.StackOverflowError
 * -Xss256k
 */
public class Stack2StackOverflowError {
    private static int count;

    public static void main(String[] args) {
        try {
            method1();
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println(count);
        }
    }

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

The error is as follows:

java.lang.StackOverflowError

VM options 

Configurable configurations parameter VM options (internal configuration parameters)

Such as: in vm 

-Xms512m -Xmx512m -XX:PermSize=64M -XX:MaxPermSize=256m

Each item is separated by a space

Parameter Description

-Xms768m: Set the JVM initial heap memory to 768m. This value can be set the same as -Xmx to avoid JVM reallocation of memory after each garbage collection is completed

-Xmx768m: Set the maximum heap memory of the JVM to 768m.
-Xss128k: Set the stack size of each thread. After JDK5.0, the size of each thread stack is 1M, and before that, the size of each thread stack is 256K. It should be adjusted according to the memory size required by the application's threads. Under the same physical memory, reducing this value can generate more threads. However, the operating system still has a limit on the number of threads in a process, and it cannot be generated infinitely. The experience value is around 3000~5000. It should be noted that when this value is set to a large value (for example > 2MB), it will greatly reduce the performance of the system.
-Xmn2g: Set the young generation size to 2G. When the size of the entire heap memory is determined, increasing the young generation will reduce the old generation, and vice versa. This value is related to JVM garbage collection and has a great impact on system performance. The official recommended configuration is 3/8 of the entire heap size.
-XX:NewSize=1024m: Set the initial value of the young generation to 1024M.
-XX:MaxNewSize=1024m: Set the maximum value of the young generation to 1024M.
-XX:PermSize=256m: Set the initial value of the persistent generation to 256M.
-XX:MaxPermSize=256m: Set the maximum value of the persistent generation to 256M.
-XX:NewRatio=4: Set the ratio of the young generation (including 1 Eden and 2 Survivor areas) to the old generation. Indicates that the ratio of the young generation to the old generation is 1:4.
-XX:SurvivorRatio=4: Set the ratio of the Eden area to the Survivor area in the young generation. Indicates that the ratio of 2 Survivor areas (the JVM heap memory young generation has 2 Survivor areas of equal size by default) to 1 Eden area is 2:4, that is, 1 Survivor area accounts for 1/6 of the entire young generation size.
-XX:MaxTenuringThreshold=7: Indicates that if an object moves 7 times in the Survivor area (rescue space) and has not been garbage collected, it will enter the old generation. If it is set to 0, the young generation objects will directly enter the old generation without going through the Survivor area. For applications that require a large amount of resident memory, this can improve efficiency. If this value is set to a larger value, the young generation object will be copied multiple times in the Survivor area, which can increase the survival time of the object in the young generation, increase the probability of the object being garbage collected in the young generation, and reduce the frequency of Full GC , which can improve service stability to some extent.
Standard parameters, all JVMs must support the functions of these parameters, and are backward compatible; for example:
-client——Set the JVM to use the Client mode, which is characterized by a relatively fast startup speed, but the runtime performance and memory management efficiency are not high, usually used For client applications or development and debugging; this mode is enabled by default when running Java programs directly in a 32-bit environment.
-server——Set the JVM to Server mode, which is characterized by slow startup speed, but high runtime performance and memory management efficiency, suitable for production environments. This mode is enabled by default on 64-bit capable JDK environments.
Non-standard parameters (-X), the default JVM implements the functions of these parameters, but not guaranteed to be satisfied by all JVM implementations, and does not guarantee backward compatibility;
Unstable parameters (-XX), each JVM implementation of such parameters will vary Different, may not be supported in the future, need to be used with caution

2. The CPU usage is too high 

First run a java file to get the .class file and upload it to the VM virtual machine

/**
 * cpu 占用过高
 */
public class Stack3CPUFull {
    public static void main(String[] args) {
        new Thread(null, () -> {
            System.out.println("1...");
            while(true) {

            }
        }, "thread1").start();


        new Thread(null, () -> {
            System.out.println("2...");
            try {
                Thread.sleep(1000000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "thread2").start();

        new Thread(null, () -> {
            System.out.println("3...");
            try {
                Thread.sleep(1000000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "thread3").start();
    }
}
# 后台运行java程序
nohup java Stack3CPUFull &

# top命令查看CPU使用情况;定位哪个进程对cpu的占用过高
top 

# ps命令进一步定位是哪个线程引起的cpu占用过高
ps H -eo pid,tid,%cpu | grep 进程id

# 根据线程id 找到有问题的线程,进一步定位到问题代码的源码行号
jstack 进程id

Convert the thread ID 32665 to the hexadecimal number 7F99 to locate the thread that occupies too much CPU and the number of lines where the execution code error is located

3. Thread private lock (the program has not been returned for a long time)

/**
 * 线程死锁
 */
class A{};
class B{};
public class Stack4ThreadDeadLock {
    static A a = new A();
    static B b = new B();


    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            synchronized (a) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (b) {
                    System.out.println("我获得了 a 和 b");
                }
            }
        }).start();
        Thread.sleep(1000);
        new Thread(()->{
            synchronized (b) {
                synchronized (a) {
                    System.out.println("我获得了 a 和 b");
                }
            }
        }).start();
    }
}

Three, the local method stack Navite Method Stacks

The method with the native keyword requires JAVA to call the local C or C++ method, because sometimes JAVA cannot directly interact with the bottom layer of the operating system, so it needs to use the native method stack to serve the method with the native keyword

There are many methods of navite modification in the common root class Object

The function and characteristics of the local method stack are similar to the virtual machine stack, both have the characteristics of thread isolation and can throw StackOverflowError and OutOfMemoryError exceptions

The difference is that the object served by the native method stack is the native method executed by the JVM, while the virtual machine stack serves the java method executed by the JVM. The HotSpot virtual machine does not distinguish between the virtual machine stack and the local method stack, the two are one piece

Four, heap Heap

The largest memory area managed by the JVM stores object instances and is a thread shared area (through the new keyword, objects will be created using heap memory)

 The heap is the main area managed by the garbage collector, hence the name "GC heap"

Classification of JAVA heap:

(1) From the perspective of memory recovery, it can be divided into the new generation (Eden space, From Survivor space, To Survivor space) and the old generation (Tenured Gen)

  • The heap memory is divided into 两块one piece 年轻代and another piece 老年代.

  • The young generation is further divided into Edenand survivor. The ratio of their space size is 8:2 by default,

  • Survival area is divided into s0(From Space and s1(To Space . These two spaces are exactly the same size, they are a pair of twins, the ratio of them is 1:1

The young generation is divided into Eden and Survivor areas. The Survivor area consists of From Space and To Space. The Eden area occupies a large capacity, and the Survivor area occupies a small capacity. The default ratio is 8:1:1

The default ratio of the old generation to the young generation is 2:1

(2) From the perspective of memory allocation, in order to solve the problem of thread safety when allocating memory, multiple thread-private allocation buffers (TLAB) may be divided in the JAVA heap shared by threads

The JAVA heap can be in a physically discontinuous memory space, as long as it is logically continuous.

The size of the heap memory at runtime can be specified through the parameters -Xmx -Xms, and an OutOfMemoryError exception will be thrown if the heap memory space is insufficient

(1) Heap memory overflow OutOfMemoryError

/**
 * 堆内存溢出 java.lang.OutOfMemoryError: Java heap space
 * -Xmx8m
 */
public class Heap1OutOfMemoryError {
    public static void main(String[] args) {
        int i = 0;
        try {
            List<String> list = new ArrayList<>();
            String a = "hello";
            while (true) {
                list.add(a); // hello, hellohello, hellohellohellohello ...
                a = a + a;  // hellohellohellohello
                i++;
            }
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println(i);
        }
    }
}

java.lang.OutOfMemoryError: Java heap space 

(2) Heap memory diagnosis

/**
 * 堆内存
 */
public class Heap2Memory {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("1...");
        //在此休眠30s 是为了 输出 jmap - heap 进程id 命令
        Thread.sleep(30000);

        byte[] array = new byte[1024 * 1024 * 10]; // 10 Mb

        System.out.println("2...");
        Thread.sleep(30000);

        array = null;

        System.gc();
        System.out.println("3...");
        Thread.sleep(1000000L);
    }
}

1. jps tool

Check which java processes exist in the current system

jps

2. jmap tool

View heap memory usage

Execute the jmap - heap process id command after the console outputs 1, 2, and 3 respectively

jmap - heap 进程id

You may encounter an error:

Error attaching to process: sun.jvm.hotspot.runtime.VMVersionMismatchException: Supported versions are 25.291-b10. Target VM is 25.342-b07
sun.jvm.hotspot.debugger.DebuggerException: sun.jvm.hotspot.runtime.VMVersionMismatchException: Supported versions are 25.291-b10. Target VM is 25.342-b07

solution:

1. Specify the path when using 

D:\JDK\jdk1.8.0_291\bin\jmap -heap 21352

或

C:\Users\zhangm\.jdks\corretto-1.8.0_342\bin\jmap

2. Keep the command java -version的JDK, the 程序运行的JDKsame as 

3 times the output is as follows: 

D:\Java\JavaProject\jvm-demo\myjvm>jps
23092 RemoteMavenServer36
9460 Jps
20664 Launcher
23640 Heap2Memory
6040 Launcher
13020 RemoteMavenServer36
15852

D:\Java\JavaProject\jvm-demo\myjvm>C:\Users\zhangm\.jdks\corretto-1.8.0_342\bin\jmap -heap 23640
Attaching to process ID 23640, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.342-b07

using thread-local object allocation.
Parallel GC with 8 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 6377439232 (6082.0MB)
   NewSize                  = 133169152 (127.0MB)
   MaxNewSize               = 2125463552 (2027.0MB)
   OldSize                  = 267386880 (255.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 100663296 (96.0MB)
   used     = 6074064 (5.7926788330078125MB)
   free     = 94589232 (90.20732116699219MB)
   6.034040451049805% used
From Space:
   capacity = 16252928 (15.5MB)
   used     = 0 (0.0MB)
   free     = 16252928 (15.5MB)
   0.0% used
To Space:
   capacity = 16252928 (15.5MB)
   used     = 0 (0.0MB)
   free     = 16252928 (15.5MB)
   0.0% used
PS Old Generation
   capacity = 267386880 (255.0MB)
   used     = 0 (0.0MB)
   free     = 267386880 (255.0MB)
   0.0% used

1706 interned Strings occupying 175328 bytes.

D:\Java\JavaProject\jvm-demo\myjvm>C:\Users\zhangm\.jdks\corretto-1.8.0_342\bin\jmap -heap 23640
Attaching to process ID 23640, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.342-b07

using thread-local object allocation.
Parallel GC with 8 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 6377439232 (6082.0MB)
   NewSize                  = 133169152 (127.0MB)
   MaxNewSize               = 2125463552 (2027.0MB)
   OldSize                  = 267386880 (255.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 100663296 (96.0MB)
   used     = 16559840 (15.792694091796875MB)
   free     = 84103456 (80.20730590820312MB)
   16.45072301228841% used
From Space:
   capacity = 16252928 (15.5MB)
   used     = 0 (0.0MB)
   free     = 16252928 (15.5MB)
   0.0% used
To Space:
   capacity = 16252928 (15.5MB)
   used     = 0 (0.0MB)
   free     = 16252928 (15.5MB)
   0.0% used
PS Old Generation
   capacity = 267386880 (255.0MB)
   used     = 0 (0.0MB)
   free     = 267386880 (255.0MB)
   0.0% used

1707 interned Strings occupying 175376 bytes.

D:\Java\JavaProject\jvm-demo\myjvm>C:\Users\zhangm\.jdks\corretto-1.8.0_342\bin\jmap -heap 23640
Attaching to process ID 23640, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.342-b07

using thread-local object allocation.
Parallel GC with 8 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 6377439232 (6082.0MB)
   NewSize                  = 133169152 (127.0MB)
   MaxNewSize               = 2125463552 (2027.0MB)
   OldSize                  = 267386880 (255.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 100663296 (96.0MB)
   used     = 4026576 (3.8400421142578125MB)
   free     = 96636720 (92.15995788574219MB)
   4.000043869018555% used
From Space:
   capacity = 16252928 (15.5MB)
   used     = 0 (0.0MB)
   free     = 16252928 (15.5MB)
   0.0% used
To Space:
   capacity = 16252928 (15.5MB)
   used     = 0 (0.0MB)
   free     = 16252928 (15.5MB)
   0.0% used
PS Old Generation
   capacity = 267386880 (255.0MB)
   used     = 830432 (0.791961669921875MB)
   free     = 266556448 (254.20803833007812MB)
   0.31057320389093135% used

1691 interned Strings occupying 174248 bytes.

 

3. jconsole tool

Graphical interface, multi-function monitoring tool, can monitor continuously

After running the program, the following command is output on the console

jconsole

 

4. jvisualvm tool

5. Method Area Method Area

Official website address

Chapter 2. The Structure of the Java Virtual Machine

Like the Java heap, the method area is a memory area shared by each thread. It is used to store data such as class information, constants, static variables, and code compiled by the instant compiler that have been loaded by the virtual machine.

Before JDK8, its implementation of the method area was called the permanent generation , which used a part of the heap as the method area

After JDK8, the implementation of the permanent generation was removed, and a metaspace implementation was replaced. The metaspace used part of the operating system (some memory) as the method area instead of being part of the heap.

(1) Method area structure

 (2) Memory overflow in the method area

1. Prior to 1.8, it would cause permanent generation memory overflow

Permanent generation memory overflow  java.lang.OutOfMemoryError: PermGen space
-XX:MaxPermSize=8m

2. After 1.8, it will cause memory overflow in metaspace

Metaspace memory overflow  java.lang.OutOfMemoryError: Metaspace
-XX:MaxMetaspaceSize=8m

 (3) Runtime constant pool

1. Constant pool

The constant pool can also be called the Class constant pool. Each.javafile is compiled to generate.classa file. Each.classfile contains a constant pool. This constant pool is defined in the Class file and.javawill not change after the file is compiled. , and cannot be modified, so it is called a static constant pool


The constant pool is a table, and the virtual machine instruction finds the class name, method name, parameter type, literal value and other information to be executed according to this constant table

2. Runtime constant pool

The constant pool is  *.class in the file. 符号地址When the bytecode of a class is loaded into the memory, its constant pool information will be concentrated into a piece of memory, which is called the runtime constant pool, and the variables inside为真实地址

There is a one-to-one correspondence between the runtime constant pool and the constant pool of the class file, and it is built by the constant pool of the class file.

There are two types in the runtime constant pool, symbolic references and static constants.

Among them, static constants do not require subsequent parsing, while symbolic references require further parsing

static constant, symbolic reference

String site="www.com"

The string "www.com" can be regarded as a static constant, because it will not change, and it will display whatever it is.

The name "site" of the above string is a symbol reference, which needs to be parsed during runtime, because the value of site can change, we cannot determine its real value at the first time, and it needs to be parsed during dynamic running

We write a basic java program of Hello World, run it and compile it into a .class bytecode file, switch to the directory where the .class file is located in the console, and execute

javap -v HelloWorld

The console output is as follows:

D:\Java\JavaProject\jvm-demo\myjvm>cd out/production/myjvm/com/mycompany

D:\Java\JavaProject\jvm-demo\myjvm\out\production\myjvm\com\mycompany>javap -v HelloWorld
警告: 二进制文件HelloWorld包含com.mycompany.HelloWorld
Classfile /D:/Java/JavaProject/jvm-demo/myjvm/out/production/myjvm/com/mycompany/HelloWorld.class
  Last modified 2022-9-27; size 562 bytes
  MD5 checksum 56139c042931911e7cea84a4ece0987c
  Compiled from "HelloWorld.java"
public class com.mycompany.HelloWorld
  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            // com/mycompany/HelloWorld
   #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               Lcom/mycompany/HelloWorld;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               HelloWorld.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               com/mycompany/HelloWorld
  #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 com.mycompany.HelloWorld();
    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   Lcom/mycompany/HelloWorld;

  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: "HelloWorld.java"

3. Static constants

Static constants in the runtime constant pool are constructed from constant_pool in the class file. Can be divided into two parts:

String constants and numeric constants

(1) String constant

The String constant is a reference to the String object and is constructed from the CONSTANT_String_info structure in the class

CONSTANT_String_info {
    u1 tag;
    u2 string_index;
}

The content of the class constant pool corresponding to string_index is a CONSTANT_Utf8_info structure 

CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

CONSTANT_Utf8_info is the variant UTF-8 encoding of the String object to be created 

(2) Numeric constants

Numeric constants are constructed from CONSTANT_Integer_info, CONSTANT_Float_info, CONSTANT_Long_info and CONSTANT_Double_info in the class file

4. Symbol reference

Symbolic references are also constructed from the constant_pool in the class.

Symbolic references to class and interface come from CONSTANT_Class_info.

References to fields in class and interface come from CONSTANT_Fieldref_info.

The reference to the method in the class comes from CONSTANT_Methodref_info.

The reference to the method in the interface comes from CONSTANT_InterfaceMethodref_info.

The reference to the method handle comes from CONSTANT_MethodHandle_info.

References to method types come from CONSTANT_MethodType_info.

Symbolic references to dynamically computed constants come from CONSTANT_MethodType_info.

The reference to the dynamically calculated call site comes from CONSTANT_InvokeDynamic_info

(4) StringTable

1. Characteristics of StringTable

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

Use the string pool mechanism 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

The intern method actively puts string objects that are not in the string pool into the string pool

[1] 1.8 Try to put this string object into the string pool, if there is, it will not be put into the string pool, if not, it will be put into the string pool, and the reference of the object in the string pool will be returned

[2] 1.6 Try to put this string object into the string pool, if there is, it will not be put in, if not, it will make a copy of this object (a new string object), put it into the string pool, and put the string The object in the pool returns

JDK1.8

// StringTable [ "a", "b" ,"ab" ]  hashtable 结构,不能扩容
public class StringTable1 {
    public static void main(String[] args) {
        // 常量池中的信息,都会被加载到运行时常量池中, 这时 a b ab 都是常量池中的符号,还没有变为 java 字符串对象
        // ldc #2 会把 a 符号变为 "a" 字符串对象
        // ldc #3 会把 b 符号变为 "b" 字符串对象
        // ldc #4 会把 ab 符号变为 "ab" 字符串对象

        String s1 = "a"; // 懒惰的
        String s2 = "b";

        String s3 = "ab";
        String s4 = s1 + s2;// new StringBuilder().append("a").append("b").toString() ----> new String("ab") 堆内存中新对象
        String s5 = "a" + "b";// javac 在编译期间的优化,结果已经在编译期确定为ab(单纯的字符串拼接)

        System.out.println(s3 == s4);//false
        System.out.println(s3 == s5);//true
    }
}
D:\Java\JavaProject\jvm-demo\myjvm>cd out/production/myjvm/com/mycompany/stringtable
D:\Java\JavaProject\jvm-demo\myjvm\out\production\myjvm\com\mycompany\stringtable>javap -v Stringtable1
警告: 二进制文件Stringtable1包含com.mycompany.stringtable.StringTable1
Classfile /D:/Java/JavaProject/jvm-demo/myjvm/out/production/myjvm/com/mycompany/stringtable/Stringtable1.class
  Last modified 2022-9-27; size 1045 bytes
  MD5 checksum 92716b83ac90d0a1d2798c17959679f0
  Compiled from "StringTable1.java"
public class com.mycompany.stringtable.StringTable1
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #12.#36        // java/lang/Object."<init>":()V
   #2 = String             #37            // a
   #3 = String             #38            // b
   #4 = String             #39            // ab
   #5 = Class              #40            // java/lang/StringBuilder
   #6 = Methodref          #5.#36         // java/lang/StringBuilder."<init>":()V
   #7 = Methodref          #5.#41         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #8 = Methodref          #5.#42         // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #9 = Fieldref           #43.#44        // java/lang/System.out:Ljava/io/PrintStream;
  #10 = Methodref          #45.#46        // java/io/PrintStream.println:(Z)V
  #11 = Class              #47            // com/mycompany/stringtable/StringTable1
  #12 = Class              #48            // java/lang/Object
  #13 = Utf8               <init>
  #14 = Utf8               ()V
  #15 = Utf8               Code
  #16 = Utf8               LineNumberTable
  #17 = Utf8               LocalVariableTable
  #18 = Utf8               this
  #19 = Utf8               Lcom/mycompany/stringtable/StringTable1;
  #20 = Utf8               main
  #21 = Utf8               ([Ljava/lang/String;)V
  #22 = Utf8               args
  #23 = Utf8               [Ljava/lang/String;
  #24 = Utf8               s1
  #25 = Utf8               Ljava/lang/String;
  #26 = Utf8               s2
  #27 = Utf8               s3
  #28 = Utf8               s4
  #29 = Utf8               s5
  #30 = Utf8               StackMapTable
  #31 = Class              #23            // "[Ljava/lang/String;"
  #32 = Class              #49            // java/lang/String
  #33 = Class              #50            // java/io/PrintStream
  #34 = Utf8               SourceFile
  #35 = Utf8               StringTable1.java
  #36 = NameAndType        #13:#14        // "<init>":()V
  #37 = Utf8               a
  #38 = Utf8               b
  #39 = Utf8               ab
  #40 = Utf8               java/lang/StringBuilder
  #41 = NameAndType        #51:#52        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #42 = NameAndType        #53:#54        // toString:()Ljava/lang/String;
  #43 = Class              #55            // java/lang/System
  #44 = NameAndType        #56:#57        // out:Ljava/io/PrintStream;
  #45 = Class              #50            // java/io/PrintStream
  #46 = NameAndType        #58:#59        // println:(Z)V
  #47 = Utf8               com/mycompany/stringtable/StringTable1
  #48 = Utf8               java/lang/Object
  #49 = Utf8               java/lang/String
  #50 = Utf8               java/io/PrintStream
  #51 = Utf8               append
  #52 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #53 = Utf8               toString
  #54 = Utf8               ()Ljava/lang/String;
  #55 = Utf8               java/lang/System
  #56 = Utf8               out
  #57 = Utf8               Ljava/io/PrintStream;
  #58 = Utf8               println
  #59 = Utf8               (Z)V
{
  public com.mycompany.stringtable.StringTable1();
    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   Lcom/mycompany/stringtable/StringTable1;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=6, args_size=1
         0: ldc           #2                  // String a
         2: astore_1
         3: ldc           #3                  // String b
         5: astore_2
         6: ldc           #4                  // String ab
         8: astore_3
         9: new           #5                  // class java/lang/StringBuilder
        12: dup
        13: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
        16: aload_1
        17: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        20: aload_2
        21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        27: astore        4
        29: ldc           #4                  // String ab
        31: astore        5
        33: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
        36: aload_3
        37: aload         4
        39: if_acmpne     46
        42: iconst_1
        43: goto          47
        46: iconst_0
        47: invokevirtual #10                 // Method java/io/PrintStream.println:(Z)V
        50: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
        53: aload_3
        54: aload         5
        56: if_acmpne     63
        59: iconst_1
        60: goto          64
        63: iconst_0
        64: invokevirtual #10                 // Method java/io/PrintStream.println:(Z)V
        67: return
      LineNumberTable:
        line 5: 0
        line 6: 3
        line 8: 6
        line 9: 9
        line 10: 29
        line 12: 33
        line 13: 50
        line 14: 67
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      68     0  args   [Ljava/lang/String;
            3      65     1    s1   Ljava/lang/String;
            6      62     2    s2   Ljava/lang/String;
            9      59     3    s3   Ljava/lang/String;
           29      39     4    s4   Ljava/lang/String;
           33      35     5    s5   Ljava/lang/String;
      StackMapTable: number_of_entries = 4
        frame_type = 255 /* full_frame */
          offset_delta = 46
          locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String
, class java/lang/String ]
          stack = [ class java/io/PrintStream ]
        frame_type = 255 /* full_frame */
          offset_delta = 0
          stack = [ class java/io/PrintStream, int ]
        frame_type = 79 /* same_locals_1_stack_item */
          stack = [ class java/io/PrintStream ]
        frame_type = 255 /* full_frame */

          locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]
          stack = [ class java/io/PrintStream, int ]
}
SourceFile: "StringTable1.java"

 intern() method

1.8 Try to put this string object into the string pool, if there is, it will not be put into the string pool, if not, it will be put into the string pool, and the object in the string pool will be returned

1.6 Try to put this string object into the string pool, if there is, it will not be put in, if not, it will make a copy of this object, put it into the string pool, and return the object in the string pool

JDK1.8

public class StringTable2 {
    public static void main(String[] args) {
        //  ["ab", "a", "b"]

        // 对比 s == x false
        //String x = "ab";
        String s = new String("a") + new String("b");

        // 堆  new String("a")   new String("b") new String("ab")
        //1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
        //1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池, 会把串池中的对象返回
        String s2 = s.intern();

        // 对比 s == x true
        String x = "ab";

        System.out.println( s2 == x);//true
        System.out.println( s == x );
    }
}
/**
 * 字符串相关分析
 */
public class StringTable3 {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "a" + "b"; // 字符串拼接 ----> "ab";javac 在编译器的优化,结果在编译器已经确定的
        String s4 = s1 + s2; // new StringBuilder().append("a").append("b").toString() ----> new String("ab")
        String s5 = "ab";
        /*
        JDK1.8:intern()方法将这个字符串对象尝试放入StringTable串池中;如果有不会放入,如果没有则放入串池中,会把串池对象返回
        JDK1.6:intern()方法将这个字符串对象尝试放入StringTable串池中;如果有不会放入,如果没有则会复制一个对象放入串池中,会把串池对象返回
        * */
        String s6 = s4.intern();

        System.out.println(s3 == s4);//false
        System.out.println(s3 == s5);//true
        System.out.println(s3 == s6);//true



        // 堆中 new String("c") ; new String("d") ;new StringBuilder().append("c").append("d").toString() ----> new String("cd")
        String x2 = new String("c") + new String("d");

        //对比 x1 == x2 false
//        String x1 = "cd";
//        x2.intern();

        //调换最后两行代码位置(true) 对比 x1 == x2 true
        //JDK1.8:intern()方法将这个字符串对象尝试放入StringTable串池中;如果有不会放入,如果没有则放入串池中,会把串池对象返回
        x2.intern();
        String x1 = "cd";

        //JDK1.8 如果调换最后两行代码位置(true)
        System.out.println(x1 == x2);

    }
}

JDK1.6

public class StringTable2 {
    public static void main(String[] args) {

        // 串池中 ["ab", "a", "b"]
        //对比 s == x false
        //String x = "ab";
        String s = new String("a") + new String("b");

        // 堆  new String("a")   new String("b") new String("ab")
        //1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
        //1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池, 会把串池中的对象返回
        String s2 = s.intern();
        // s 拷贝一份,放入串池(一个新对象;s指向的"ab"地址和s2指向的"ab"地址不是同一份)

        // 串池中 ["a", "b","ab"]
        //对比 s == x false
        String x = "ab";

        System.out.println( s2 == x);//true
        System.out.println( s == x );//false
    }
}
/**
 * 字符串相关分析
 */
public class StringTable3 {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "a" + "b"; // 字符串拼接 ----> "ab";javac 在编译器的优化,结果在编译器已经确定的
        String s4 = s1 + s2; // new StringBuilder().append("a").append("b").toString() ----> new String("ab")
        String s5 = "ab";
        /*
        JDK1.8:intern()方法将这个字符串对象尝试放入StringTable串池中;如果有不会放入,如果没有则放入串池中,会把串池对象返回
        JDK1.6:intern()方法将这个字符串对象尝试放入StringTable串池中;如果有不会放入,如果没有则会复制一个对象放入串池中,会把串池对象返回
        * */
        String s6 = s4.intern();

        System.out.println(s3 == s4);//false
        System.out.println(s3 == s5);//true
        System.out.println(s3 == s6);//true

        // 堆中 new String("c") ; new String("d") ;new StringBuilder().append("c").append("d").toString() ----> new String("cd")
        String x2 = new String("c") + new String("d");

        // 对比 x1 == x2 false
        String x1 = "cd";
        x2.intern();

        //如果调换最后两行代码位置 对比 x1 == x2 false
//        x2.intern();
//        String x1 = "cd";

        //JDK1.6 如果调换最后两行代码位置(false)
        System.out.println(x1 == x2);//false
    }
}

2. StringTable position

The location of jdk1.6 StringTable is in the permanent generation, and the location of 1.8 StringTable is in the heap

JDK1.8 

/**
 * StringTable 位置
 * 在jdk8下设置 -Xmx10m -XX:-UseGCOverheadLimit
 *      java.lang.OutOfMemoryError: Java heap space
 *      单独设置 -Xmx10m 报错
 *      Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
 *      java.lang.OutOfMemoryError: GC overhead limit exceeded ,超出了GC开销限制。科普了一下,这个是JDK6新添的错误类型。是发生在GC占用大量时间为释放很小空间的时候发生的,是一种保护机制。一般是因为堆太小,导致异常的原因:没有足够的内存。
 *      官方对此的定义:超过98%的时间用来做GC并且回收了不到2%的堆内存时会抛出此异常
 * 在jdk6下设置 -XX:MaxPermSize=10m
 *      java.lang.OutOfMemoryError: PermGen space 
 */
public class StringTable4Location {
    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<String>();
        int i = 0;
        try {
            for (int j = 0; j < 260000; j++) {
                list.add(String.valueOf(j).intern());
                i++;
            }
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            System.out.println(i);
        }
    }
}

java.lang.OutOfMemoryError: GC overhead limit exceeded

GC overhead limit exceeded. Popular science, this is a new type of error added by JDK6. It happens when the GC takes up a lot of time to release a small space, and it is a protection mechanism. Usually because the heap is too small, the cause of the exception: not enough memory 

The official definition of this: This exception will be thrown when more than 98% of the time is spent on GC and less than 2% of the heap memory is reclaimed 

java.lang.OutOfMemoryError: Java heap space

JDK1.6 

java.lang.OutOfMemoryError: PermGen space 

3. StringTable Garbage Collection

/**
 * StringTable 垃圾回收
 * 在在JDK1.8下VM设置
 *      -Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc
 *          -Xmx10m          设置虚拟机堆内存大小
 *          -XX:+PrintStringTableStatistics  打印字符串表的统计信息
 *          -XX:+PrintGCDetails -verbose:gc  打印垃圾回收详细信息参数
 * 在JDK1.6下VM设置
 *      -XX:MaxPermSize=10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc
 *          -XX:MaxPermSize=10m      设置虚拟机堆内存大小
 *          -XX:+PrintStringTableStatistics    打印字符串表的统计信息
 *          -XX:+PrintGCDetails -verbose:gc    打印垃圾回收详细信息参数
 */
public class StringTable5GC {
    // 字符串常量池中默认1688个字符串 
    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        try {
//            for (int j = 0; j < 100000; j++) { // j=100, j=10000
//                String.valueOf(j).intern();
//                i++;
//            }
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            System.out.println(i);
        }

    }
}

JDK1.8

(1) Do nothing in try

Heap
 PSYoungGen      total 2560K, used 727K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  eden space 2048K, 11% used [0x00000000ffd00000,0x00000000ffd3bc80,0x00000000fff00000)
  from space 512K, 95% used [0x00000000fff00000,0x00000000fff7a020,0x00000000fff80000)
  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
 ParOldGen       total 7168K, used 379K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
  object space 7168K, 5% used [0x00000000ff600000,0x00000000ff65efb8,0x00000000ffd00000)
 Metaspace       used 3214K, capacity 4556K, committed 4864K, reserved 1056768K
  class space    used 336K, capacity 392K, committed 512K, reserved 1048576K
Disconnected from the target VM, address: '127.0.0.1:62455', transport: 'socket'
SymbolTable statistics:
Number of buckets       :     20011 =    160088 bytes, avg   8.000
Number of entries       :     13428 =    322272 bytes, avg  24.000
Number of literals      :     13428 =    605144 bytes, avg  45.066
Total footprint         :           =   1087504 bytes
Average bucket size     :     0.671
Variance of bucket size :     0.668
Std. dev. of bucket size:     0.817
Maximum bucket size     :         6
StringTable statistics:
Number of buckets       :     60013 =    480104 bytes, avg   8.000
Number of entries       :      1688 =     40512 bytes, avg  24.000
Number of literals      :      1688 =    174104 bytes, avg 103.142
Total footprint         :           =    694720 bytes
Average bucket size     :     0.028
Variance of bucket size :     0.028
Std. dev. of bucket size:     0.168
Maximum bucket size     :         3

There are 1688 strings in the string constant pool by default 

(2) Loop 100 times in try, the number of string constants + 100

(3) 100,000 loops in try, which triggers the garbage collection mechanism GC, and there are only 28,000+ strings

JDK1.6

(1) Do nothing in try

0
Heap
 def new generation   total 4928K, used 1243K [0x10030000, 0x10580000, 0x15580000)
  eden space 4416K,  28% used [0x10030000, 0x10166d20, 0x10480000)
  from space 512K,   0% used [0x10480000, 0x10480000, 0x10500000)
  to   space 512K,   0% used [0x10500000, 0x10500000, 0x10580000)
 tenured generation   total 10944K, used 0K [0x15580000, 0x16030000, 0x20030000)
   the space 10944K,   0% used [0x15580000, 0x15580000, 0x15580200, 0x16030000)
 compacting perm gen  total 12288K, used 2537K [0x20030000, 0x20c30000, 0x20c30000)
   the space 12288K,  20% used [0x20030000, 0x202aa608, 0x202aa800, 0x20c30000)
No shared spaces configured.
Disconnected from the target VM, address: '127.0.0.1:63518', transport: 'socket'
SymbolTable statistics:
Number of buckets       :   20011
Average bucket size     :       0
Variance of bucket size :       0
Std. dev. of bucket size:       1
Maximum bucket size     :       6
StringTable statistics:
Number of buckets       :    1009
Average bucket size     :       1
Variance of bucket size :       1
Std. dev. of bucket size:       1
Maximum bucket size     :       7

(2) 100,000 cycles in try, triggering the garbage collection mechanism GC, only 28,000+ strings

4. StringTable performance tuning

1. Adjust -XX:StringTableSize=number of buckets

Set the bucket size (the bucket is the subscript element of the array index); JDK1.6 defaults to 1009, after JDK1.7 the default is 60013, starting from JDK1.8 1009 is the minimum value that can be set

The bottom layer of the string constant pool is HashTable (the HashTable class implements a hash table, which maps keys to corresponding values; the bottom layer of HashTable is the same as HashMap; JDK1.6 array + one-way linked list; JDK1.8 array + one-way Linked list + red-black tree), reasonably increasing the size of the constant pool will solve the Hash conflict problem

The larger the number of buckets, the more efficient it is to search for the linked list or red-black tree elements subscripted by the array index (the fewer elements on the linked list, the shorter the traversal time)

Copy a text file containing nearly 480,000 strings, run according to the default configuration, it takes 336 milliseconds

/**
 * StringTableSize串池大小对性能的影响
 * -Xms500m -Xmx500m -XX:+PrintStringTableStatistics -XX:StringTableSize=1009
 *      -Xms500m    设置堆内存最小值
 *      -Xmx500m    设置堆内存最大值
 *      -XX:+PrintStringTableStatistics     字符串常量池统计信息
 *      -XX:StringTableSize=1009
 *          设置桶大小(桶即数组索引下标元素);JDK1.6默认为1009,JDK1.7之后默认为60013,JDK1.8开始1009是可以设置的最小值
 *          字符串常量池底层为HashTable(HashTable类实现一个哈希表,该哈希表将键映射到相应值;HashTable底层与HashMap原理相同;JDK1.6 数组+单向链表;JDK1.8 数组 + 单向链表 + 红黑树),合理增大常量池大小会解决Hash冲突问题
 */
public class StringTable6Optimize {
    public static void main(String[] args) throws IOException {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("myjvm/linux.words"), "utf-8"))) {
            String line = null;
            long start = System.nanoTime();
            while (true) {
                line = reader.readLine();
                if (line == null) {
                    break;
                }
                line.intern();
            }
            System.out.println("花费时间:" + (System.nanoTime() - start) / 1000000 + "毫秒");
        }
    }
}

Set the VM parameters, set the bucket size StringTableSize = 1009 minimum value, it takes 8066 milliseconds

Set VM parameters, set the bucket size StringTableSize = 200000, it takes 314 milliseconds

2. Whether to put some string objects into the pool

public class StringTable7OptimizeIntern {
    public static void main(String[] args) throws IOException {

        List<String> address = new ArrayList<>();
        //System.in.read();
        for (int i = 0; i < 10; i++) {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("myjvm/linux.words"), "utf-8"))) {
                String line = null;
                long start = System.nanoTime();
                while (true) {
                    line = reader.readLine();
                    if(line == null) {
                        break;
                    }
                    address.add(line.intern());
                }
                System.out.println("花费时间:" + (System.nanoTime() - start) / 1000000 + "毫秒");
            }
        }
        //System.in.read();
    }
}

Six, direct memory Direct Memory

Direct Memory Direct memory does not belong to JVM management, it is operating system memory

(1) Commonly used in NIO operations for data buffers (for example, ByteBuffer uses direct memory)

(2) Allocation and recovery costs are high, but read and write performance is high

(3) Not subject to JVM memory recovery management

(1) File (IO) read and write process

1. Traditional way

Java itself does not have the ability to read and write disks. To achieve disk reads and writes, you must call functions provided by the operating system (that is, local methods). Here, the state of the CPU changes from user state (Java) to kernel state (system) [call After the functions provided by the system]

There will also be some related operations on the memory side. After switching to the kernel state, he can use the CPU function to actually read the contents of the disk file. In the kernel state, after reading the content, he will be in the operating system. Draw a buffer in the memory, which is called the system buffer, and the contents of the disk are read into the system buffer first (read in batches). The Java code in the system buffer cannot run, so Java is in the system buffer. Allocate a Java buffer in the heap memory, that is, new byte[size] in the code. In order for the Java code to be able to access the data in the stream just read, it must read the data from the system buffer into the Java buffer indirectly, then the state of the CPU switches to the user state, and then calls the Java The write operation of the output stream, just read and write repeatedly, and copy the entire file to the target location.

Since there are two pieces of memory and two buffers, that is, both the system memory and the Java heap memory have buffers, the data must be stored in two copies when reading. It is not enough to read the system buffer first, because The Java code cannot access them, so the system buffer data is read into the Java buffer, which causes an unnecessary data copy, and the efficiency is not very high

In short: Java cannot directly operate file management, you need to switch to the kernel mode, use the local method to operate, and then read the disk file, a buffer will be created in the system memory, read the data into the system buffer, and then System buffer data, copied to java heap memory. The disadvantage is that the data is stored in two copies, one in the system memory and one in the java heap, resulting in unnecessary copying

2. Use directBuffer

When ByteBuffer calls the allocateDirect method, it will draw a buffer area on the operating system side, that is, direct memory. The difference between this area and the previous one is that the memory allocated by the operating system can be directly accessed by Java code, that is, the system You can access it, and Java code can also access it, that is, it is a memory area that can be shared by both system memory and Java code, which is direct memory.

That is, after the disk file is read into the direct memory, the Java code directly accesses the direct memory, which saves one copy operation in the buffer than the traditional code, so the speed is doubled. This is also the benefit of direct memory, which is suitable for this kind of io operation of files

In short: direct memory is an area that both the operating system and Java code can access, eliminating the need to copy code from system memory to Java heap memory, thus improving efficiency

/**
 * ByteBuffer 作用
 */
public class ByteAndDirectBuffer {
    static final String FROM = "D:\\文件\\SQL基本介绍.avi";
    static final String TO = "D:\\a.mp4";
    static final int _1Mb = 1024 * 1024;

    public static void main(String[] args) {
        io(); // io 用时:71.7764;53.8123;107.987
        directBuffer(); // directBuffer 用时:40.2163;30.3857;46.0605
    }

    private static void directBuffer() {
        long start = System.nanoTime();
        try (FileChannel from = new FileInputStream(FROM).getChannel();
             FileChannel to = new FileOutputStream(TO).getChannel();
        ) {
            ByteBuffer bb = ByteBuffer.allocateDirect(_1Mb);
            while (true) {
                int len = from.read(bb);
                if (len == -1) {
                    break;
                }
                bb.flip();
                to.write(bb);
                bb.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.nanoTime();
        System.out.println("directBuffer 用时:" + (end - start) / 1000_000.0);
    }

    private static void io() {
        long start = System.nanoTime();
        try (FileInputStream from = new FileInputStream(FROM);
             FileOutputStream to = new FileOutputStream(TO);
        ) {
            byte[] buf = new byte[_1Mb];
            while (true) {
                int len = from.read(buf);
                if (len == -1) {
                    break;
                }
                to.write(buf, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.nanoTime();
        System.out.println("io 用时:" + (end - start) / 1000_000.0);
    }
}

(2) Memory overflow OOM

java.lang.OutOfMemoryError: Direct buffer memory

/**
 * 直接内存溢出
 * java.lang.OutOfMemoryError: Direct buffer memory
 */
public class Direct2OutOfMemory {
    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);
        }
        /*
        方法区是jvm规范
            jdk6 中对方法区的实现称为永久代
            jdk8 对方法区的实现称为元空间
        * */
    }
}

(3) Distribution and recovery principles

Because the direct memory is not managed by the JVM, it is impossible to monitor the memory usage using tools such as jmap, jconsole, jvisualvm, etc. You need to check it in the "Task Manager"

/**
 * 禁用显式回收对直接内存的影响
 */
public class Direct3GC {
    static int _1Gb = 1024 * 1024 * 1024;

    /*
     * -XX:+DisableExplicitGC 显式的
     */
    public static void main(String[] args) throws IOException {
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1Gb);

        System.out.println("分配完毕...");

        System.in.read();

        System.out.println("开始释放...");

        byteBuffer = null;
        System.gc(); // 显式的垃圾回收,Full GC

        System.in.read();
    }
}

start allocating memory 

free memory 

Note:

There is a misunderstanding here, don't think that you see the call 

System.gc(); // explicit garbage collection, Full GC

When the direct memory is reclaimed, it is assumed that the direct memory is managed by the JVM. In fact, the underlying Unsafe object plays a role

If the VM is set

-XX:+DisableExplicitGC

System.gc() will be disabled, causing the collection to fail

Because Java garbage collection is not done, although byteBuffer is null, it is still alive because of sufficient memory. Since he is alive, the corresponding direct memory (ByteBuffer.allocateDirect(-1Gb)) has not been reclaimed, and windows can see it from the task manager (after running the above code, a task manager will appear Java process, 1G is allocated here, so the memory occupied by that process is also 1G)

After disabling System.gc(), you will find that other codes will not be greatly affected, but the direct memory will be affected, because we cannot use the displayed method to recycle Bytebuffer, so ByteBuffer can only wait until the real garbage collection. is cleaned up, and the corresponding direct memory will also be cleaned up.

This has caused a phenomenon that the direct memory may occupy a large amount and cannot be released for a long time. When there are many direct memory usages, the way to manage direct memory is to call the freeMemory method of the Unsafe object directly when freeing the direct memory. In the end, the programmer manually manages the direct memory, so it is recommended to use the related methods of Unsafe

1. Unsafe  object allocation and memory recovery

The Unsafe  object completes the allocation and recovery of direct memory, and the recovery needs to actively call  the freeMemory  method

/**
 * 直接内存分配的底层原理:Unsafe
 */
public class Direct4Unsafe {
    static int _1Gb = 1024 * 1024 * 1024;

    public static void main(String[] args) throws IOException {
        Unsafe unsafe = getUnsafe();
        // 分配内存
        long base = unsafe.allocateMemory(_1Gb);
        unsafe.setMemory(base, _1Gb, (byte) 0);
        System.out.println("分配完毕...");
        System.in.read();

        // 释放内存
        unsafe.freeMemory(base);
        System.out.println("释放...");
        System.in.read();
    }

    public static Unsafe getUnsafe() {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            Unsafe unsafe = (Unsafe) f.get(null);
            return unsafe;
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}

Allocate memory 

release reclaimed memory

2. The principle of Unsafe allocation and recovery

The Unsafe  object completes the allocation and recovery of direct memory, and the recovery needs to actively call  the freeMemory  method

Inside the implementation class of ByteBuer , Cleaner (virtual reference)  is used   to monitor  the ByteBuer  object. Once the ByteBuer  object is garbage collected,  the ReferenceHandler thread will  call  the freeMemory method through the clean method  of the Cleaner  to release the direct memory.

ByteBuffer.allocateDirect(_1Gb);


public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer>{
    public static ByteBuffer allocateDirect(int capacity) {
        return new DirectByteBuffer(capacity);
    }
}


class DirectByteBuffer extends MappedByteBuffer implements DirectBuffer{
    DirectByteBuffer(int cap) {                   // package-private

        ......

        try {
            base = unsafe.allocateMemory(size);
        } catch (OutOfMemoryError x) {
            Bits.unreserveMemory(size, cap);
            throw x;
        }
        unsafe.setMemory(base, size, (byte) 0);
        
        ......

        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
        att = null;

    }

    
    private Deallocator(long address, long size, int capacity) {
            assert (address != 0);
            this.address = address;
            this.size = size;
            this.capacity = capacity;
        }

        public void run() {
            if (address == 0) {
                // Paranoia
                return;
            }
            unsafe.freeMemory(address);
            address = 0;
            Bits.unreserveMemory(size, capacity);
        }
}

Guess you like

Origin blog.csdn.net/MinggeQingchun/article/details/127066302