Java Virtual Machine Quick Start | JVM Introduction, JVM Memory Structure, Direct Memory

Table of contents

One: Introduction to JVM

1. What is JVM?

2. Common JVMs

3. Learning route

Two: JVM memory structure

1. Program counter (PC Register)

2. Virtual Machine Stacks (JVM Stacks)

3. Native Method Stacks

4. Heap

5. Method Area

Three: direct memory


Tips: First of all, I would like to recommend two useful free software to everyone: GIF grabbing software: ScreenToGif and screen recording tool: oCam, which can be used for daily GIF production and screen recording, network disk link: quark network disk sharing

One: Introduction to JVM

1. What is JVM?

Definition: Java Virtual Machine - the running environment of java programs (the running environment of java binary bytecode)

benefit:

① Write once, run everywhere; 

② Automatic memory management, with garbage collection function;

③Array subscript out of bounds check, throw an exception;

④ Polymorphism, the cornerstone of object-oriented.

Comparison: JVM, JRE, JDK

From the figure, we can also see that it is a step-by-step upward and inclusive relationship!

2. Common JVMs

The most commonly used ones are: HotSpot, Oracle JDK edition, Eclipse OPenJ9; the following explanations are based on HotSpot!

3. Learning route

It is mainly divided into three parts: class loader ClassLoader , JVM memory structure , and execution engine .

Learning sequence: first learn the JVM memory structure, then learn the GC garbage collection mechanism, then learn the JavaClass bytecode, then learn the class loader ClassLoader, and finally learn other content of the execution engine.

Two: JVM memory structure

1. Program counter (PC Register)

(1) Definition

ProgramCounterRegister program counter (register)

Features: It is thread-private, and there will be no memory overflow!

(2) Function

Execution process: Java source code---"Compiled to generate binary bytecode (some JVM instructions)---"After interpreter---"Interpreted into machine code---"Finally handed over to the CPU for execution!

The function of the program counter: remember the execution address of the next JVM instruction during program execution (the previous number can be understood as the execution address). For example: Get the first getstatic instruction and give it to the interpreter, the interpreter becomes machine code, and the machine code is handed over to the CPU; at the same time, the address (3) of the next instruction (astore_1) will be put into the program counter, and wait until After the first instruction is executed, the interpreter will fetch the address (3) of the next instruction (astore_1) from the program counter, and repeat in turn!

Think: What would be the problem if there is no program counter?

It will result in not knowing which command to execute next! In fact, the program counter is physically implemented through registers!

2. Virtual Machine Stacks (JVM Stacks)

Stack: It is a data structure, first-in, first-out or last-in-first-out; a thread has a stack!

(1) Definition

Java Virtual Machine Stacks (Java virtual machine stack)

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

② Each stack is composed 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 method currently being executed.

Summarize:

Stack ---"corresponds to the memory space required by the thread to run .

Stack frame ---"corresponds to the memory space required for each method to run .

We can understand the stack and stack frame through the following piece of code, through Debug mode

problem analysis:

(1) Does garbage collection involve stack memory?

Answer: No, the stack frame memory will be popped up every time the method ends, and the stack will be automatically released and recycled ; we know that the garbage collection mechanism can only reclaim useless objects in the heap memory.

(2) Is the larger the stack memory allocation, the better?

Answer: No, the larger the stack memory, the smaller the number of threads will be , because the size of the physical memory is fixed. For example: if a thread is allocated 1M, and the total physical memory is 500M, theoretically only 500 threads can be allocated; if a thread is allocated 2M, theoretically only 250 threads can be allocated!

Note: The stack memory division is large, but more method calls can be made, and the operating efficiency will not be improved!

Note: At runtime, you can use -Xss size to specify the size of the allocated stack memory ; by default: Linux and macOS allocate 1024KB, and Windows is allocated according to the size of virtual memory

(3) Are the local variables in the method thread-safe?

Example 1: Does analyzing multiple thread calls mess up the value of variable x?

We know that one thread has one stack, and calling different threads will generate new stack frames, and each thread has its own private variable x.

Example 2: Analyzing methods called by multiple threads, can thread safety be guaranteed?

①M1 method, local variable, does not escape the scope of the method, and the variable is released at the end of the method, which is thread-safe;

②m2 method, the variable in the method (the premise is to refer to the data type), other threads can call through this method, which is not thread-safe;

③m3 method, return this local variable (provided that the reference data type) is returned through return, other threads can receive it, and then perform other operations;

Summarize:

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

②If the local variable is a reference type (the basic data type must still be thread-safe), and escape the scope of the method, thread safety needs to be considered;

(2) Stack memory overflow

Case 1: Too many stack frames lead to stack memory overflow

The size of the stack is fixed. If we continue to call, so that the stack frame is continuously pushed onto the stack, it will eventually lead to stack memory overflow; for example: recursive calls, no end conditions, and eventually a StackOverflowError exception is thrown !

Case 2: The stack frame is too large to cause stack memory overflow

The stack frame is too large, exceeding the size of the stack at once; rare!

(3) Thread running diagnosis

Case 1: Too much cpu usage (possibly an infinite loop)

Positioning: In the Linux environment, run Java code, nohub java class&

nohub: means not to hang up. When using Linux client tools such as Xshell to remotely execute Linux scripts, sometimes due to network problems, the client will lose connection, the terminal will be disconnected, and the script will end unexpectedly half way through. In this case, you can use the nohup command to run the command, even if the client is disconnected from the server, the script on the server can still continue to run.

&: means to run in the background.

First use the top command to locate which process occupies too much CPU

ps H -eo pid,tid,%cpu | grep process id ( Use the ps command to further locate which thread caused the high CPU usage)

H: Displays a tree structure, indicating the relationship between processes

-eo: specifies the output of those interested content, for example: process id (pid), thread id (tid), CPU usage (%cpu)

|: represents the pipe character, often used together with the grep filter command

jstack process id: You can find the problematic thread according to the thread id, and further locate the source code line number of the problem code!

Note: The 32665 thread number displayed above is in decimal, and the hexadecimal displayed by jstack corresponds to 7F99

Case 2: The program runs for a long time without results (there may be a thread deadlock)

Execute the Java program first, nohub java class &, the process id will be displayed

 jstack process id : At this time, we cannot know the thread id, see the prompt of the execution result at the end

When does a thread deadlock occur?

For a class with attributes a and b, for thread t1, lock a first, then lock b; for thread t2, lock b first, then lock a; in this case, the program will be deadlocked, and no exception will be thrown. Troubleshooting the case is very difficult!

3. Native Method Stacks

Definition: When the JVM calls native methods, it needs to provide memory space for these native methods!

Explain native methods (Native Method): Refers to those methods that are not written in Java code, for example: using native methods written in C and C++ to deal with the operating system, Java code can call these underlying functions through these native methods; only The memory used to write native methods is the native method stack!

For example: the clone method of the Object class

4. Heap

The stack Stack learned earlier is private to the thread, and the heap Heap is shared by the thread!

Heap: Through the new keyword, creating objects will use heap memory

Features:

①It is shared by threads, and all objects in the heap need to consider thread safety issues;

② There is a garbage collection mechanism;

(1) Heap memory overflow

First look at the following code:

First create an ArrayList collection, write an infinite loop, the strings are continuously spliced, and then put them into the List collection!

public class Demo1_5 {

    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);
        }
    }
}

Execution result: memory overflow, OutOfMemoryError is thrown

You can use -Xmx size to specify the size of the allocated heap space

(2) Heap memory diagnosis

①jps tool: check which java processes exist in the current system

②jmap tool: Check the heap memory usage at a certain moment;  jmap -heap process id

③jconsole tool: graphical interface, multi-functional monitoring tool, which can monitor continuously

Case 1:

First create a byte array, which will open up 10M space in the heap memory; then set the reference arr of the array to null, and turn on the garbage collection mechanism for recycling; the sleep sleep in the middle is to facilitate the execution of instructions for monitoring.

public class Demo1_4 {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("1...");
        Thread.sleep(30000);
        byte[] array = new byte[1024 * 1024 * 10]; // 10 Mb
        System.out.println("2...");
        Thread.sleep(20000);
        array = null;
        System.gc();
        System.out.println("3...");
        Thread.sleep(1000000L);
    }
}

Use IDEA to run this program, open the built-in dos window, and enter the command

①Enter the jps command first to see which Java processes exist

② Use jmap for detection

Step 1: Use jmap -heap 18756 to detect when the console prints out 1, that is, when the 10M memory space is not created

Step 2: Print 2 on the console, use jmap -heap 18756 to detect (create 10M space at this time)

Step 3: Print 3 on the console, use jmap -heap 18756 to check again (the reference is set to null at this time), and enable the garbage collection mechanism for recycling

 ③Use jconsole for detection (graphical interface for display)

Steps: Directly enter jconsole ---> display the graphical interface, find the class to be detected --- "select unsafe connection; it will dynamically display the detection effect at each moment!

Case 2: After calling garbage collection, the memory usage is still high

Here is the code first. If we don’t know the specific implementation of the code, how can we check it step by step?

import java.util.ArrayList;
import java.util.List;

public class ClazzMapperTest {

    public static void main(String[] args) throws InterruptedException {
        List<Student> students = new ArrayList<>();
        for (int i = 0; i < 200; i++) {
            students.add(new Student());
        }
        Thread.sleep(1000000000L);
    }
}
class Student {
    private byte[] big = new byte[1024*1024];
}

The first step: use jps to view the id of the process

Step 2: Use jmap -head process id to view memory usage; it is divided into two parts:

Eden District: 

Old Generation Ward: 

Step 3: Use the jconsole tool to execute the garbage collection mechanism GC; it is found that a part of the memory has indeed been recovered compared to the original state, but there are still more than 200 M that have not been recovered!

Step 4: In fact, more than 200 M, the Eden area has indeed been recycled a lot, but the Old Generation area has not been recycled; use a more useful detection tool jvisualvm (it will not be available after JDK9, you need to download a plug-in) for detection

①Finding the heap dump means grabbing a snapshot of the current heap

② Find the top 20 objects that occupy the largest heap memory

③ It can be found that the object occupying the largest heap memory is an ArrayList object

④Click to go to ArrayList, and check that its properties are all Student objects  ; there are 244 items in total, of which 200 are Student items, and the others are Object objects (which have been released); a Student object occupies about 1M, 200 It takes up more than 200 megabytes, so it can be checked out.

⑤Combined with the source code analysis , before the execution of the main method main ends (the sleep method is called to sleep), a large number of Student objects are stored in the ArrrayList collection, which cannot be released; finally, after garbage collection, the memory usage is still high!

5. Method Area

(1) Definition

(1) The method area is analogous to the storage area used for compiled code in traditional languages, or to the "text" segment in an operating system process. It stores per-class structures such as runtime constant pools, field and method data, and code for methods and constructors, including special methods used in class and instance initialization and interface initialization. The method area is created when the virtual machine starts . The method area is logically a part of the heap . The memory in the method area cannot satisfy the allocation request, and the Java virtual machine will throw OutOfMemoryError.

(2) Features:

①The method area is shared by threads. If multiple threads use the same class, if the class has not been loaded, only one thread can load the class at this time, and other threads need to wait;

②The size of the method area can be non-fixed, and the jvm can be dynamically adjusted according to the application needs. The jvm also supports users and programs to specify the initial size of the method area;

③ There is a garbage collection mechanism in the method area. If some classes are no longer used, they will become garbage and need to be cleaned up.

(2) Composition

JVM1.6 version memory structure:

Use a PermGen permanent generation as the implementation of the method area. This permanent generation includes the following information: Class class information, ClassLoader class loader information, StringTable (string table) runtime constant pool ;

JVM1.8 version memory structure:

Use a Metaspace metaspace as the implementation of the method area to store the following information: Class class information, ClassLoader class loader information, constant pool (different from the above); no heap memory is occupied, in other words, it is not by the JVM Managed its memory structure; moved to local memory (OS memory)

 (3) Memory overflow in the method area

①JDK1.8 before will cause permanent generation memory overflow

We did not set the upper limit of the memory, it will load all 10000 classes into the memory, you can use parameters to set, specify the size of the source space memory: -XX:MaxPermSize=8m

package cn.itcast.jvm.t1.metaspace;

import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;

// 把下面10000个类加载到内存当中
public class Demo1_8 extends ClassLoader { // 可以用来加载类的二进制字节码
    public static void main(String[] args) {
        int j = 0;
        try {
            Demo1_8 test = new Demo1_8();
            for (int i = 0; i < 20000; i++, j++) {
                // ClassWriter 作用是生成类的二进制字节码
                ClassWriter cw = new ClassWriter(0);
                // 版本号, public, 类名, 包名, 父类, 接口
                cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
                // 返回 byte[]
                byte[] code = cw.toByteArray();
                // 执行了类的加载
                test.defineClass("Class" + i, code, 0, code.length); // Class 对象
            }
        } finally {
            System.out.println(j);
        }
    }
}

Adding -XX:MaxPermSize=8m only loops 19314 times, and a permanent generation overflow exception is thrown

②JDK1.8 will cause memory overflow in metaspace

The same code, use parameters to set, specify the size of the source space memory: -XX:MaxMetaspaceSize=8m

Adding -XX:MaxMetaspaceSize=8m only loops 5411 times, and throws a metaspace memory overflow

(4) Runtime constant pool

① First understand the constant pool

For binary bytecode , including : basic class information, constant pool, class method definition, including virtual machine instructions; first look at the following code, compile and generate HelloWorld.class file, use: javap -v HelloWorld.class to decompile

package cn.itcast.jvm.t5;
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("hello world");
    }
}

The constant pool is a table . 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, for example:  

②Runtime constant pool

The constant pool is in the *.class file; when the class is loaded, its constant pool information will be put into the runtime constant pool, and the symbol address inside will be changed to a real address.

(5)StringTable

StringTable features:

① 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;

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

For 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 .

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

Verify the above properties:

String s1 = "a";
String s2 = "b";
String s3 = "ab";

To decompile:

#2 corresponds to String a, #3 corresponds to String b, and #4 corresponds to String ab

 astore_1 stores the loaded string object in the No. 1 local variable s1, and so on

The constant pool exists in the bytecode file .class, and will be placed in the runtime constant pool when running; but when loaded into the runtime constant pool, it has not yet become a java string object until it is specifically executed to refer to it That line of code; for example: when String s1 = "a" is executed, ldc #2 will change the a symbol into an "a" string object; at this time, a space StringTable will be prepared, and the "a" string object will be placed Go in (if there is no one in it), this is actually a lazy loading (lazy) behavior; if there is one in the string pool, it will be used directly, in a word, there will only be one copy!

So: "a", "b", and "ab" pointed to by s1, s2, and s3 will be placed in the string constant pool StringTable, and the bottom layer of StringTable [ "a", "b" , "ab" ] is a hashtable structure , cannot be expanded.

String s1 = "a";
String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2;

s4=s1+s2, variable splicing, s4 reference will first create a StringBuilder object, then call the append method, splicing "a" and "b", and then call the toString method; we check the underlying source code of StringBuilder's toString method The discovery is to create a new string object: new String("ab").

String s1 = "a";
String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2;
// 问
System.out.println(s3 == s4);

Result of s3 == s4?

The "ab" corresponding to s3 is an object in the string constant pool, but s4 is a newly created string object. Although the value is the same, s3 is in the string pool, and s4 is created first, = = The comparison is the address, it must be different, and the result is false.

String s1 = "a";
String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2; // 变量拼接
String s5 = "a" + "b"; // 常量拼接
// 问
System.out.println(s3 == s4); // false
System.out.println(s3 == s5); // true

s5 = "a" + "b" is directly looking for the spliced ​​"ab" object; this is the optimization of javac in the compiler. During compilation, we can be sure that it must be the "ab" object. At this time, in the constant pool This object already exists, so the result of s3 == s5 is true.

Note: s4=s1+s2 can only be determined during operation, go for dynamic splicing!

JDK1.8: Will 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!

String s = new String("a") + new String("b");
// 使用JDK1.8,会将这个字符串对象尝试放入串池,
// 如果有则不会放入,如果没有则放入串池,并把串池中的对象返回
String s2 = s.intern();
System.out.println(s2 == "ab"); // true
System.out.println(s == "ab"); // true

s = new String("a") + new String("b"); First, "a" and "b" will be put into the constant pool, but s="ab" will not be put in because it is variable splicing, A string object will be created, which is stored in the heap. If you want to put "ab" in the constant pool, you can call the intern method, so that you can put "ab" in the string constant pool. At this point, put the object of s into the constant pool, and s2 is the return value of the object in the string pool; so both are true.

String s = new String("a") + new String("b");

String x = "ab";

String s2 = s.intern();
System.out.println(s2 == x); // true
System.out.println(s == x); // false

At this time, String x = "ab' will put "ab" into the string pool; at this time, the "ab" object already exists in the string pool, and at this time s.intern() will not put the "ab" object into In the string pool; and s2 = s.intern returns must be an object in the string pool; so at this time s2 == x is true, s == x is false.

JDK1.6: Try to put this string object into the string pool, if there is, it will not be put in, if not, it will copy this object, put it into the string pool, and return the object in the string pool!

String s = new String("a") + new String("b");
// 使用JDK1.6,会将这个字符串对象尝试放入串池,
// 如果有则并不会放入,如果没有会把此对象复制一份,放入串池, 会把串池中的对象返回
String s2 = s.intern();
System.out.println(s2 == "ab"); // true
System.out.println(s == "ab"); // false

The main difference of using JDK1.6 is s.intern. At this time, a copy is put into the string pool, instead of putting the object of s itself into the string pool. s is still an object in the heap. At this time, s == " ab" is false.

Classic interview questions:

String s1 = "a";
String s2 = "b";
String s3 = "a" + "b"; // ab
String s4 = s1 + s2; // new String("ab")
String s5 = "ab";
String s6 = s4.intern();
// 问
System.out.println(s3 == s4); // false
System.out.println(s3 == s5); // true
System.out.println(s3 == s6); // true
String x2 = new String("c") + new String("d"); // new String("cd")
String x1 = "cd"; // "cd"
x2.intern();
System.out.println(x1 == x2); // false

// 问,如果调换了x1,x2的位置呢?如果是jdk1.6呢?
String x2 = new String("c") + new String("d"); // new String("cd")
x2.intern(); //先把“ab”入常量池
String x1 = "cd"; 
System.out.println(x1 == x2); 
// 此时对于JDK1.8-true,对于JDK1.6-false

(6) The location of StringTable

For JDK1.6

For JDK1.8

 So can the position of StringTable be intuitively reflected through the code?

import java.util.ArrayList;
import java.util.List;

public class Demo1_6 {

    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);
        }
    }
}

For JDK1.6, set the permanent generation memory to be smaller: -XX:MaxPermSize=10m

For JDK1.8, set the heap memory to be smaller: -Xmx10m. At this time, there is no heap memory error error; the following prompt indicates that 98% of the energy is used to recycle, but the value is reclaimed by 2%, and this error message will be reported. .

At this time, you need to add a parameter to close this prompt  -Xmx10m -XX:-UseGCOverheadLimit

(7) Garbage collection mechanism of StringTable

StringTable is also managed by the garbage collection mechanism. When the memory space is insufficient, those string constants in StringTable that have not been referenced will be recycled by the garbage collector!

Setting parameters: -Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc

-Xmx10m: Set the maximum value of the virtual machine heap memory;

-XX:+PrintStringTableStatistics: Print the statistical information of the string table;

-XX:+PrintGCDetails -verbose:gc: Print some information about garbage collection (if garbage collection occurs);

package cn.itcast.jvm.t1.stringtable;

import java.util.ArrayList;
import java.util.List;

public class Demo1_7 {
    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);
        }

    }
}

①Do not add the loop code first, check the storage of StringTable at this time

②If the cycle is 100 times, the size of the heap memory has not been exceeded at this time, and the garbage collection mechanism will not be triggered

③If the cycle is 10,000 times, the size of the heap memory has been exceeded at this time, and the garbage collection mechanism will be triggered for recycling

 The print information of the garbage collection mechanism is started:

(8) StringTable performance tuning

Method 1: Adjust -XX:StringTableSize = number of buckets

The underlying layer of StringTable is a hash table (array + linked list). The performance of the hash table is closely related to its size: if the number of buckets in the hash table is large, the elements will be scattered, and the probability of hash collision It will be reduced, and the search rate will be faster. If the number of buckets is small, the probability of hash collision will increase, resulting in a longer linked list, and the search speed will be affected!

package cn.itcast.jvm.t1.stringtable;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * 演示串池大小对性能的影响
 * -Xms500m -Xmx500m -XX:+PrintStringTableStatistics -XX:StringTableSize=1009
 */
public class Demo1_24 {

    public static void main(String[] args) throws IOException {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("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("cost:" + (System.nanoTime() - start) / 1000000); // 毫秒
        }


    }
}

The so-called tuning, the most important thing is to adjust the number of buckets : -XX:StringTableSize = number of buckets , without setting the maximum value of the memory of the virtual machine, more than 40,000 data can be easily entered into the pool!

-XX:StringTableSize = 200000, adjust the number of buckets to 200000

② Without the -XX:StringTableSize parameter, the default bucket size used is 60013

Conclusion: The smaller the number of buckets, the more time-consuming; and the minimum number of buckets is 1009!

Method 2: Consider whether to put the string object into the pool

Assume that a large number of string objects are created now, for example: there are 48,000 strings in the linux.words file, and the cycle is broken 10 times, and the memory usage of the pool and the inferior pool are compared.

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

public class Demo1_25 {

    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("linux.words"), "utf-8"))) {
                String line = null;
                long start = System.nanoTime();
                while (true) {
                    line = reader.readLine();
                    if(line == null) {
                        break;
                    }
                    // 不入池
                    address.add(line);
                    // 入池
                    address.add(line.intern());
                }
                System.out.println("cost:" +(System.nanoTime()-start)/1000000);
            }
        }
        System.in.read();


    }
}

①address.add(line) does not enter the pool. At this time, 480,000 data are added to the List collection.

Using the jvisualvm tool, select the sampler:

Graphical display of memory usage:

Before reading, the memory occupied by the string at this time is about 1M:

 After reading, the memory occupied by the string at this time is about 110M:

②Address.add(line.intern()) enters the pool. After entering the pool, the data in the next 9 cycles are repeated, and only the data entered into the pool for the first time can be used directly. After reading at this time, String+ The created char array is only less than 40M, which greatly saves the heap memory space!

Three: direct memory

(1) Definition

Direct memory does not belong to the memory in the Java virtual machine, it is the memory of the operating system!

Direct memory: DirectMemory

① Commonly used in NIO operation, used for data buffer; 

② Allocation and recovery costs are high, but read and write performance is high; 

③ Not subject to JVM memory recovery management;

 Case: Comparison using traditional IO streams and direct memory

package cn.itcast.jvm.t1.direct;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * 演示 ByteBuffer 作用
 */
public class Demo1_9 {
    static final String FROM = "E:\\编程资料\\text.txt";
    static final String TO = "E:\\a.txt";
    static final int _1Mb = 1024 * 1024;

    public static void main(String[] args) {
        io(); // io 用时:3秒左右
        directBuffer(); // directBuffer 用时:1秒左右
    }
    // 使用直接内存的方式
    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);
    }
    // 传统的IO流
    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);
    }
}

We will find that the speed of copying files (especially large files) using direct memory ByteBuffer is significantly faster than traditional IO streams, and we will analyze it from the process of reading and writing files!

For traditional IO streams:

Java itself does not have the ability to read and write disks, and must call functions provided by the operating system; that is, calling from Java methods to local methods; at this time, the CPU will switch from user mode to kernel mode; at this time, disk files can be read At this time, a layer of buffer (system buffer) will be drawn in the operating system, and the contents of the disk will be read into this system buffer first (separate reading, and Java code cannot read the system buffer) Buffer content), at this time Java will also allocate a Java buffer in the heap memory; if Java wants to read data, it must first read the data from the system cache into the Java buffer; two buffers, equivalent Two copies must be saved when reading (causing unnecessary data duplication), which is less efficient!

For direct memory:

When ByteBuffer calls the allocateDirect method, a buffer (direct memory) will be drawn between the operating systems, and Java code in this area can be directly accessed; this memory can be directly accessed by both the operating system and Java code. A shared memory area; there is only one buffer read, which is more efficient!

It is not managed by JVM memory recovery, so direct memory will also cause memory overflow, for example:

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;


// 演示直接内存溢出
public class Demo1_10 {
    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 对方法区的实现称为元空间
    }
}

Improper use of direct memory can also lead to memory overflow:

(2) Principles of allocation and recovery

example:

public class Test {
    static int _1Gb = 1024 * 1024 * 1024;

    public static void main(String[] args) throws IOException {
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1Gb); // 分配1G的空间
        System.out.println("分配完毕...");
        System.in.read();
        System.out.println("开始释放...");
        byteBuffer = null; // 空引用
        System.gc(); // 启动垃圾回收
        System.in.read();
    }
}

View task manager, allocate 1G:

Check the task manager, set it to null, start the garbage collection mechanism, and find that it has been recycled! Didn't it say that direct memory is not managed by JVM memory recycling? Why is the direct memory reclaimed and released after garbage collection?

This requires an explanation of the release principle of direct memory: first obtain the unsafe object in some way, and the allocation and recycling of direct memory can be completed through the unsafe object!

Note: For direct memory monitoring, you cannot use those monitoring tools in IDEA, you need to look at the process in the task manager

Allocate memory:

long base = unsafe.allocateMemory(_1Gb);
unsafe.setMemory(base, _1Gb, (byte) 0);

free memory:

unsafe.freeMemory(base);

The bottom layer of the ByteBuffer.allocateDirect method is the implementation class DirectByteBuffer that uses ByteBuffer

In the constructor of DirectByteBuffer, the unsafe allocateMemory method is called to allocate direct memory

The constructor of DirectByteBuffer also uses Cleaner (virtual reference) to monitor the ByteBuffer object. Once the ByteBuffer object is garbage collected, the ReferenceHandler thread will execute the task object Deallocator through the clean method of the Cleaner. The task object calls the unsafe object. freeMemory to free direct memory

-XX:+DisableExplicitGC: Disable the impact of explicit recycling on direct memory, which is to make System.gc() invalid, but at this time it will affect the release of direct memory, we can use unsafe objects to manually release direct memory!

Guess you like

Origin blog.csdn.net/m0_61933976/article/details/129228283