JVM tuning practice and constant pool explanation

1, Alibaba Arthas details

Arthas is a Java diagnostic tool open source by Alibaba in September 2018. JDK6 + is supported, and the command line interaction mode is adopted, which can easily locate and diagnose online program operation problems. The official documents of Arthas are very detailed. See: https://alibaba.github.io/arthas

Arthas usage scenario

Thanks to the powerful and rich functions of Arthas, what Arthas can do is beyond imagination. Here are just a few common usage scenarios. You can explore more usage scenarios after you are familiar with Arthas.
1. Is there a global perspective to view the health of the system?
2. Why does the CPU rise again? Where does it occupy the CPU?
3. Is there a deadlock in the running multithread? Is there a blockage?
4. The program takes a long time to run. Where does it take a long time? How to monitor?
5. Which jar package is this class loaded from? Why are all kinds of related exceptions reported?
6. Why didn't the code I changed execute? Am I not commit ted? Wrong branch?
7. You can't debug online when you encounter a problem. Can you only republish it by adding a log?
8. Is there any way to monitor the real-time running status of the JVM?

Use of Arthas

# github download arthas
wget https://alibaba.github.io/arthas/arthas-boot.jar
# Or Gitee Download
wget https://arthas.gitee.io/arthas-boot.jar

Run it with Java jar, and you can identify all Java processes on the machine (we have run an Arthas test program here before, and the code is shown below)

package com.tuling.jvm;

import java.util.HashSet;

public class Arthas {

    private static HashSet hashSet = new HashSet();

    public static void main(String[] args) {
        // Analog CPU too high
        cpuHigh();
        // Simulate thread deadlock
        deadThread();
        // Continuously add data to the hashSet set
        addHashSetThread();
    }

    /**
     * Constantly add data to the hashSet set
     */
    public static void addHashSetThread() {
        // Initialization constant
        new Thread(() -> {
            int count = 0;
            while (true) {
                try {
                    hashSet.add("count" + count);
                    Thread.sleep(1000);
                    count++;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    public static void cpuHigh() {
        new Thread(() -> {
            while (true) {

            }
        }).start();
    }

    /**
     * deadlock
     */
    private static void deadThread() {
        /** Create resource */
        Object resourceA = new Object();
        Object resourceB = new Object();
        // Create thread
        Thread threadA = new Thread(() -> {
            synchronized (resourceA) {
                System.out.println(Thread.currentThread() + " get ResourceA");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resourceB");
                synchronized (resourceB) {
                    System.out.println(Thread.currentThread() + " get resourceB");
                }
            }
        });

        Thread threadB = new Thread(() -> {
            synchronized (resourceB) {
                System.out.println(Thread.currentThread() + " get ResourceB");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resourceA");
                synchronized (resourceA) {
                    System.out.println(Thread.currentThread() + " get resourceA");
                }
            }
        });
        threadA.start();
        threadB.start();
    }
}

Select process serial number 1 to enter the process information operation

Enter dashboard to view the running status of the whole process, including thread, memory, GC and running environment information:

Enter thread to view the thread details

Enter thread plus thread ID to view the thread stack

Enter thread -b to view thread deadlocks


Enter the full name of jad plus class to decompile, which makes it easy for us to check whether the online code is the correct version

Use the ognl command to view the values of online system variables and even modify the values of variables


For more commands, you can use the help command to view or view the document: https://alibaba.github.io/arthas/commands.html#arthas

GC log details

For java applications, we can print out all the GC logs during program operation through some configurations, and then analyze the GC logs to obtain key indicators, analyze the causes of GC, and tune JVM parameters.
Print GC log method, add parameters in JVM parameters,% t represents time

-Xloggc:./gc-%t.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps  -XX:+PrintGCTimeStamps -XX:+PrintGCCause  
-XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M

Tomcat is directly added to Java_ In the opts variable.

How to analyze GC logs
Run the program plus the corresponding gc log

java -jar -Xloggc:./gc-%t.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps  -XX:+PrintGCTimeStamps -XX:+PrintGCCause  
-XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M microservice-eureka-server.jar

In the following figure, I intercepted a part of the GC log of the JVM just started

We can see that the red box in the first line of the figure is the configuration parameters of the project. Not only print GC logs, but also related VM memory parameters are configured here.
The red box in the second line shows the relevant GC after the GC occurs at this GC time point.
1. For 2.909: This is the elapsed time from the start of the jvm to this GC, preceded by the specific occurrence time and date.
2. Full GC(Metadata GC Threshold) means that this is a full gc. The reason for GC is in parentheses. PSYoungGen is a young generation GC, ParOldGen is an old age GC, and Metaspace is a meta space GC
3. 6160k - > 0k (141824k). These three numbers correspond to the size of the young generation occupied before GC, the young generation occupied after GC, and the size of the whole young generation.
4. 112k - > 6056k (95744k). These three numbers respectively correspond to the size of the old age occupied before GC, the old age occupied after GC, and the size of the whole old age.
5. 6272k - > 6056k (237568k). These three numbers correspond to the heap memory occupied before GC, the heap memory occupied after GC, and the size of the whole heap memory respectively.
6. 20516k - > 20516k (1069056k). These three numbers respectively correspond to the size of meta space memory occupied before GC, meta space memory occupied after GC, and the size of the whole meta space memory.
7. 0.0209707 is the total GC time consumed at this time point.

It can be found from the log that several fullgc times are caused by insufficient meta space, so we can increase the meta space

java -jar -Xloggc:./gc-adjust-%t.log -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+PrintGCDetails -XX:+PrintGCDateStamps  
-XX:+PrintGCTimeStamps -XX:+PrintGCCause  -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M 
microservice-eureka-server.jar

After adjustment, we can look at the gc log and find that there is no fullgc caused by insufficient meta space

The logs of CMS and G1 collectors are a little different. You can also try to print the corresponding gc log. After analysis, you can find that the gc steps in the gc log are similar to those mentioned earlier

public class HeapTest {

    byte[] a = new byte[1024 * 100];  //100KB

    public static void main(String[] args) throws InterruptedException {
        ArrayList<HeapTest> heapTests = new ArrayList<>();
        while (true) {
            heapTests.add(new HeapTest());
            Thread.sleep(10);
        }
    }
}

CMS

-Xloggc:d:/gc-cms-%t.log -Xms50M -Xmx50M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+PrintGCDetails -XX:+PrintGCDateStamps  
 -XX:+PrintGCTimeStamps -XX:+PrintGCCause  -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M 
 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC   

G1

-Xloggc:d:/gc-g1-%t.log -Xms50M -Xmx50M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+PrintGCDetails -XX:+PrintGCDateStamps  
 -XX:+PrintGCTimeStamps -XX:+PrintGCCause  -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M -XX:+UseG1GC 

The above parameters can help us view and analyze GC garbage collection. But if GC logs are many, many, thousands of lines. Even if you read ten lines at a glance, your mind will be blank. So we can use some functions to help us analyze. Here we recommend a gceasy( https://gceasy.io ), you can upload GC files, and then he will use the visual interface to show the GC situation. The details are shown in the figure below

In the figure above, we can see the memory allocation and maximum usage of the young generation, the old generation and the permanent generation.

In the figure above, we can see the changes before and after GC in the heap and other information.
This tool also provides JVM intelligent optimization suggestions based on machine learning. Of course, this function needs to be paid now

JVM parameter summary view command

java -XX:+PrintFlagsInitial means to print out the default values of all parameter options
java -XX:+PrintFlagsFinal means to print out the values of all parameter options that take effect when running the program

Class constant pool and runtime constant pool

The Class constant pool can be understood as a resource warehouse in the Class file. In addition to the description information such as the version, field, method and interface of the Class, the Class file also contains a constant pool table, which is used to store various literal and symbolic references generated during compilation.

The general hexadecimal structure of a class file is shown in the figure below:

The corresponding meanings are as follows. For details, you can check the official oracle documents

Of course, we generally do not manually parse this hexadecimal bytecode file. We can generally generate a more readable JVM bytecode instruction file through the javap command:
javap -v Math.class

The red box indicates the class constant pool information. The constant pool mainly stores two types of constants: literal and symbolic references.

Literal
Literal quantity refers to a string or numeric constant composed of letters, numbers, etc
Literal quantities can only appear as right values. The so-called right value refers to the value to the right of the equal sign. For example, int a=1, where a is the left value and 1 is the right value. In this example, 1 is literal.

int a = 1;
int b = 2;
int c = "abcdefg";
int d = "abcdefg";

Symbol reference

Symbolic reference is a concept in compilation principle, which is relative to direct reference. It mainly includes the following three types of constants:
Fully qualified names of classes and interfaces
Name and descriptor of the field
Name and descriptor of the method
A and b above are field names, which are symbolic references. LCOM / Turing / JVM / Math in the constant pool of Math class is the fully qualified name of the class, main and compute are method names, and () is a UTF8 descriptor. These are symbolic references.
These constant pools are now static information. Only when they are loaded into memory at runtime can these symbols have the corresponding memory address information. Once these constant pools are loaded into memory, they become runtime constant pools, and the corresponding symbol references will be transformed into direct references to the code loaded into the memory area when the program is loaded or running, that is, dynamic links. For example, the symbolic reference of compute() will be transformed into the address of the specific code of compute() method in memory at run time, and the direct reference will be transformed mainly through the type pointer in the object header.

String constant pool

Design idea of string constant pool
String allocation, like other object allocation, consumes a high cost of time and space. As the most basic data type, a large number of strings are created frequently, which greatly affects the performance of the program
In order to improve performance and reduce memory overhead, the JVM makes some optimizations when instantiating string constants
Open up a string constant pool for strings, similar to a cache
When creating a string constant, first query whether the string exists in the string constant pool
If the string exists, return the reference instance. If it does not exist, instantiate the string and put it into the pool

Three string operations (Jdk1.7 and above)

Direct assignment string

String s = "zhuge";  // s points to a reference in the constant pool

String objects created in this way will only be in the constant pool.
Because of the literal "zhuge", when creating object s, the JVM will first go to the constant pool and judge whether there are the same objects through the equals(key) method
If yes, the reference of the object in the constant pool is returned directly;
If not, a new object is created in the constant pool and a reference is returned.

new String();

String s1 = new String("zhuge");  // s1 refers to an object reference in memory

This method will ensure that this object exists in the string constant pool and heap, create it without, and finally return the object reference in heap memory.
The steps are as follows:
Because there is a literal of "zhuge", we will first check whether the string "zhuge" exists in the string constant pool
If it does not exist, first create a string object in the string constant pool; Then create a string object "zhuge" in memory;
If it exists, directly create a string object "zhuge" in the heap memory;
Finally, the reference in memory is returned.

intern method

intern method
String s1 = new String("zhuge");   
String s2 = s1.intern();

System.out.println(s1 == s2);  //false

The intern method in String is a native method. When calling the intern method, if the pool already contains a String equal to this String object (determined by the equals (object) method), the String in the pool will be returned. Otherwise, point the reference returned by intern to the current String s1 (the JDK1.6 version needs to copy s1 to the String constant pool).

String constant pool location

Jdk1.6 and before: there is a permanent generation, the runtime constant pool is in the permanent generation, and the runtime constant pool contains the string constant pool
Jdk1.7: There is a permanent generation, but it has been gradually "de permanent generation". The string constant pool is separated from the runtime constant pool in the permanent generation to the heap
Jdk1.8 and later: there is no permanent generation, the runtime constant pool is in the meta space, and the string constant pool is still in the heap
Use a program to prove where the string constant pool is:

/**
 * jdk6: -Xms6M -Xmx6M -XX:PermSize=6M -XX:MaxPermSize=6M  
 * jdk8: -Xms6M -Xmx6M -XX:MetaspaceSize=6M -XX:MaxMetaspaceSize=6M
 */
public class RuntimeConstantPoolOOM{
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<String>();
        for (int i = 0; i < 10000000; i++) {
            String str = String.valueOf(i).intern();
            list.add(str);
        }
    }
}

Operation results:
jdk7 And above: Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
jdk6: Exception in thread "main" java.lang.OutOfMemoryError: PermGen space

Design principle of string constant pool

The bottom layer of the string constant pool is implemented in C + + of hotspot. The bottom layer is similar to a HashTable, which essentially stores the references of string objects.
Look at a common interview question. How many String objects are created in the following code?

String s1 = new String("he") + new String("llo");
String s2 = s1.intern();
 
System.out.println(s1 == s2);
// In JDK 1.6, the output is false and six objects are created
// In JDK version 1.7 and above, the output is true and five objects are created
// Of course, we don't consider GC here, but these objects do exist or have existed

Why does the output change? The main reason is that the string pool is separated from the permanent generation and moved into the heap. The intern() method has also changed accordingly:
1. In JDK 1.6, calling intern() first looks for a string equal to equal() in the string pool. If the string exists, it returns the reference of the string in the string pool. If the string does not exist, the virtual opportunity will re create an instance on the permanent generation and point a table item of StringTable to the newly created instance.

2. In JDK 1.7 (and above), because the string pool is not permanently replaced, intern() has made some modifications to make it easier to use the objects in the heap. When the string exists, it is the same as JDK 1.6, but when the string does not exist, it is no longer necessary to re create the instance, and you can directly point to the instance on the heap.

From the above two figures, it is not difficult to understand why JDK 1.6 string pool overflow will throw OutOfMemoryError: PermGen space, while JDK 1.7 and above will throw OutOfMemoryError: Java heap space.

Several examples of String constant pool problem
Example 1:

String s0="zhuge";
String s1="zhuge";
String s2="zhu" + "ge";
System.out.println( s0==s1 ); //true
System.out.println( s0==s2 ); //true

Analysis: because s0 and "zhuge" in S1 in the example are string constants, they are determined at compile time, so s0s1 is true; "zhu" and "ge" are also string constants. When a string is connected by multiple string constants, it must also be a string constant, so s2 is also optimized as a string constant "zhuge" at compile time, so s2 is also a reference to "zhuge" in the constant pool. So we get s0s1==s2;

Example 2:

String s0="zhuge";
String s1=new String("zhuge");
String s2="zhu" + new String("ge");
System.out.println( s0==s1 );  // false
System.out.println( s0==s2 );  // false
System.out.println( s1==s2 );  // false

Analysis: the strings created with new String() are not constants and cannot be determined at compile time. Therefore, the strings created with new String() are not put into the constant pool. They have their own address space.
s0 is also a reference to "zhuge" in the constant pool. s1 is a reference to the new object "zhuge" created at runtime because it cannot be determined at compile time. s2 is also a reference to the newly created object "zhuge" because it has the second half of new String("ge").

Example 3:

  String a = "a1";
  String b = "a" + 1;
  System.out.println(a == b); // true 
  
  String a = "atrue";
  String b = "a" + "true";
  System.out.println(a == b); // true 
  
  String a = "a3.4";
  String b = "a" + 3.4;
  System.out.println(a == b); // true

Analysis: for the "+" connection of string constants, the JVM optimizes the "+" connection of constant strings to the connected value during program compilation. Take "a" + 1 as an example. After optimization by the compiler, it is already a1 in the class. The value of its string constant is determined during compilation, so the final result of the above program is true.

Example 4:

String a = "ab";
String bb = "b";
String b = "a" + bb;

System.out.println(a == b); // false

Analysis: for string reference, the JVM has a string reference in the "+" connection of the string, and the value of the reference cannot be determined during the compilation of the program, that is, "a" + bb "cannot be optimized by the compiler. It can only be dynamically allocated during the running of the program and assign the connected new address to b. So the result of the above program is false.

Example 5:

String a = "ab";
final String bb = "b";
String b = "a" + bb;

System.out.println(a == b); // true

Analysis: the only difference from example 4 is that the bb string is decorated with final. For the variable decorated with final, it is parsed as a local copy of the constant value at compile time, stored in its own constant pool or embedded in its byte code stream. Therefore, "a" + bb "and" a "+" B "have the same effect. Therefore, the result of the above program is true.

Example 6:

String a = "ab";
final String bb = getBB();
String b = "a" + bb;

System.out.println(a == b); // false

private static String getBB() 
{  
    return "b";  
 }

Analysis: the JVM refers to bb as a string, and its value cannot be determined at the compile time. Only after calling the method at the program run time, the return value of the method is dynamically connected with "a" and the address is assigned as b. therefore, the result of the above program is false.

String is immutable
From the above example, we can know that:

String  s  =  "a" + "b" + "c";  //It is equivalent to String s = "abc";
String  a  =  "a";
String  b  =  "b";
String  c  =  "c";
String  s1  =   a  +  b  +  c;

s1 is different. You can observe the JVM instruction code and find that the "+" operation of s1 will become the following operation:

StringBuilder temp = new StringBuilder();
temp.append(a).append(b).append(c);
String s = temp.toString();

Finally, let's take another example:

//String constant pool: "computer" and "technology" heap memory: object "computer technology" referenced by str1  
//There is also a StringBuilder object in the heap memory, but it will be recycled by gc. The toString method of StringBuilder will new String(), which is the real returned object reference
String str2 = new StringBuilder("computer").append("technology").toString();   //There is no "computer technology" literal, so the "computer technology" object will not be generated in the constant pool
System.out.println(str2 == str2.intern());  //true
//"Computer technology" does not exist in the pool, but exists in the heap. When intern, it will directly return the reference in the heap

//String constant pool: "ja" and "va" heap memory: object "java" referenced by str1  
//There is also a StringBuilder object in the heap memory, but it will be recycled by gc. The toString method of StringBuilder will new String(), which is the real returned object reference
String str1 = new StringBuilder("ja").append("va").toString();    //The "java" literal does not appear, so the "java" object will not be generated in the constant pool
System.out.println(str1 == str1.intern());  //false
//java is a keyword. It must have been put into the string constant pool in the relevant classes initialized by the JVM

String s1=new String("test");  
System.out.println(s1==s1.intern());   //false
//"Test" is put into the pool as a literal. When new, s1 points to the newly generated string object in heap, s1 Intern () refers to the string object generated in the pool before the literal "test"

String s2=new StringBuilder("abc").toString();
System.out.println(s2==s2.intern());  //false
//ditto

Eight basic types of wrapper classes and object pools

Most of the basic types of wrapper classes in java implement the constant pool technology (strictly speaking, it should be called object pool, on the heap). These classes are Byte,Short,Integer,Long,Character and Boolean. The other two floating-point wrapper classes are not implemented. In addition, the wrapper classes of byte, short, integer, long and character can only use the object pool when the corresponding value is less than or equal to 127, that is, the object is not responsible for creating and managing the objects of these classes greater than 127. Because the probability of using such a small number is relatively large.

public class Test {

    public static void main(String[] args) {
        //Five kinds of shaped packing objects such as byte, short, integer, long and character,  
        //Object pools can be used when the value is less than 127  
        Integer i1 = 127;  //The bottom layer of this call is actually an integer Valueof (127), which uses the IntegerCache object pool
        Integer i2 = 127;
        System.out.println(i1 == i2);//Output true  

        //When the value is greater than 127, objects are not fetched from the object pool  
        Integer i3 = 128;
        Integer i4 = 128;
        System.out.println(i3 == i4);//Output false  
        
        //Using the new keyword to generate a new object does not use the object pool
        Integer i5 = new Integer(127);  
        Integer i6 = new Integer(127);
        System.out.println(i5 == i6);//Output false 

        //Boolean class also implements object pool technology  
        Boolean bool1 = true;
        Boolean bool2 = true;
        System.out.println(bool1 == bool2);//Output true  

        //Floating point wrapper classes do not implement object pooling technology  
        Double d1 = 1.0;
        Double d2 = 1.0;
        System.out.println(d1 == d2);//Output false  
    }
} 




summary

Keywords: jvm

Added by komquat on Sat, 25 Dec 2021 05:34:17 +0200