Deep understanding of JVM virtual machines

Posted by immot on Sun, 13 Feb 2022 11:46:53 +0100

JVM learning

This article is based on JVM [produced by dark horse programmer] Notes made during instructional video learning

1, What is a JVM

definition

Java Virtual Machine, the running environment of JAVA program (the running environment of JAVA binary bytecode)

benefit

  • Write once and run everywhere
  • Automatic memory management, garbage collection mechanism
  • Array subscript out of bounds check

compare

Differences between JVM JRE JDK

2, Memory structure

Overall architecture

1. Program counter

effect

Used to save the address of the next instruction to be executed in the JVM (similar to a register)

characteristic

  • Thread private
    • The CPU will allocate time slices for each thread. When the time slice of the current thread is used up, the CPU will execute the code in another thread
    • The program counter is private to each thread. When the time slice of another thread runs out and returns to execute the code of the current thread, you can know which instruction should be executed through the program counter
  • There will be no memory overflow

2. Virtual machine stack

definition

  • The memory space required for each thread is called virtual machine stack

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

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

demonstration

code

public class Main {
	public static void main(String[] args) {
		method1();
	}

	private static void method1() {
		method2(1, 2);
	}

	private static int method2(int a, int b) {
		int c = a + b;
		return c;
	}
}


 

It can be seen from the console that the methods in the main class conform to the characteristics of the stack when entering the virtual machine stack

Problem Formulation

  • Does garbage collection involve stack memory?
    • unwanted. Because the virtual machine stack is composed of stack frames, after the method is executed, the corresponding stack frame will be popped out of the stack. Therefore, there is no need to recycle memory through garbage collection mechanism.
  • Is the larger the stack memory allocation, the better?
    • no Because the physical memory is certain, the larger the stack memory, the more recursive calls can be supported, but the fewer threads can be executed.
  • Are local variables in methods thread safe?
    • It is thread safe if local variables within a method do not escape the scope of the method
    • If a local variable references an object and escapes the scope of a method, you need to consider thread safety

out of memory

Java.lang.stackOverflowError: stack memory overflow

Cause of occurrence

  • Too many stack frames in the virtual machine stack (infinite recursion)
  • Each stack frame takes up too much space

Thread running diagnosis

CPU usage too high

  • When running some programs in Linux environment, it may lead to excessive CPU consumption. At this time, it is necessary to locate the threads that occupy too much CPU
    • top command to see which process is taking up too much CPU
    • ps H -eo pid, tid (thread id),% cpu | grep the process number just found through top | further check which thread occupies too much CPU through ps command
    • Jstack process id: compare and locate the nid of the thread in the process with the tid just seen through the ps command. Note that the thread id found by jstack is hexadecimal and needs to be converted

3. Local method stack

Some methods with native keywords need JAVA to call local C or C + + methods. Sometimes JAVA can not directly interact with the underlying operating system, so local methods need to be used

4. Pile

definition

Objects created with the new keyword are placed in heap memory

characteristic

  • All threads are shared, and the objects in heap memory need to consider thread safety
  • There is a garbage collection mechanism

Heap memory overflow

java.lang.OutofMemoryError : java heap space. Heap memory overflow

Heap memory diagnostics

jps

jmap

jconsole

jvirsalvm

5. Method area

structure

out of memory

  • Before 1.8, it will cause permanent generation memory overflow
  • After 1.8, it will cause meta space memory overflow

Constant pool

Composition of binary bytecode: basic information of class, constant pool, method definition of class (including virtual machine instructions)

Decompile to view class information

  • Get the of the corresponding class Class file

    • Run cmd in the bin directory corresponding to JDK, or enter it on the IDEA console

    • Enter the absolute path of the corresponding javac class

      F:\JAVA\JDK8.0\bin>javac F:\Thread_study\src\com\nyima\JVM\day01\Main.java

      After input, the class will appear in the corresponding directory Class file

  • Enter the absolute path of the javap -v class in the console

    javap -v F:\Thread_study\src\com\nyima\JVM\day01\Main.class
  • Then you can see the information of the decompiled class on the console

    • Basic information of class

    • Constant pool

    • The method of executing compilation in the virtual machine (what is in the box is the content of the actual compilation execution, # number content needs to be found in the constant pool)

Runtime Constant Pool

  • Constant pool
    • It is a table (such as constant pool in the figure above). The virtual machine instruction finds the class name, method name, parameter type and literal information to be executed according to this constant table
  • Runtime Constant Pool
    • The constant pool is After the * class in the class file is loaded, its constant pool information will be put into the runtime constant pool, and the symbolic address in it will be changed into the real address**

Relationship between constant pool and string pool

String pool StringTable

features

  • Strings in the constant pool are only symbols and will not be converted to objects until they are used
  • The string pool mechanism is used to avoid repeated creation of string objects
  • The principle of string variable splicing is StringBuilder
  • The principle of string constant splicing is compiler optimization
  • You can use the intern method to actively put string objects that are not in the string pool into the string pool
  • Note: both strings in the string pool and in the heap are objects

An element that is not used in a repeated string

public class StringTableStudy {
	public static void main(String[] args) {
		String a = "a"; 
		String b = "b";
		String ab = "ab";
	}
}

The information in the constant pool will be loaded into the runtime constant pool, but this is a b ab, which is only the symbol in the constant pool and has not become a java string

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: return

When ldc #2 is executed, the symbol a will be changed into a "a" string object and put into the string pool (hashtable structure cannot be expanded)

When ldc #3 is executed, the symbol b will be changed into a "b" string object and put into the string pool

When ldc #4 is executed, the symbol AB will be changed into "ab" string object and put into string pool

Final StringTable ["a", "b", "ab"]

Note: the creation of string objects is lazy. The string will be created and put into the string pool only when it runs to that line of string and does not exist in the string pool (such as ldc #2).

The process of creating a string using a concatenated string variable object

public class StringTableStudy {
	public static void main(String[] args) {
		String a = "a";
		String b = "b";
		String ab = "ab";
		//Splice string objects to create new strings
		String ab2 = a+b; 
	}
}

Decompiled results

	 Code:
      stack=2, locals=5, 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/Str
ing;
        27: astore        4
        29: return

The process of creating strings by splicing is: stringbuilder() append(“a”). append(“b”). toString()

The return value of the last toString method is a new string, but the value of the string is consistent with the spliced string, but there are two different strings, one in the string pool and the other in the heap memory

String ab = "ab";
String ab2 = a+b;
//The result is false because ab exists in the string pool and ab2 is an object returned by the toString method of StringBuffer and exists in heap memory
System.out.println(ab == ab2);

Creates a string using the method of splicing string constant objects

public class StringTableStudy {
	public static void main(String[] args) {
		String a = "a";
		String b = "b";
		String ab = "ab";
		String ab2 = a+b;
		//Create a string using the method of splicing strings
		String ab3 = "a" + "b";
	}
}

Decompiled results

 	  Code:
      stack=2, 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/Str
ing;
        27: astore        4
        //ab3 gets strings directly from the string pool during initialization
        29: ldc           #4                  // String ab
        31: astore        5
        33: returnCopy
  • When using the method of splicing string constants to create a new string, because the content is a constant, javac will be optimized during compilation. The result has been determined as AB during compilation, and "ab" has been put into the string pool when creating AB, so ab3 directly obtains the value from the string pool, so the operation is the same as ab = "ab".
  • When you use the method of splicing string variables to create a new string, because the content is a variable, its value can only be determined at run time, so you need to use StringBuilder to create it

intern method 1.8

Calling the intern method of the string object will try to put the string object into the string pool

  • If the string object does not exist in the string pool, it is put successfully
  • If there is this string object, it fails to be put in

The string object in the string pool will be returned no matter whether the insertion is successful or not

Note: if the intern method is called successfully at this time, the heap memory and the string object in the string pool are the same object; If it fails, it is not the same object

Example 1

public class Main {
	public static void main(String[] args) {
		//"A" and "B" are put into the string pool, and str exists in heap memory
		String str = new String("a") + new String("b");
		//Call the intern method of str. if there is no "ab" in the string pool, the string object will be put into the string pool. At this time, the heap memory and "ab" in the string pool are the same object
		String st2 = str.intern();
		//Assign a value to str3, because there is "ab" in the string pool at this time, the contents in the string pool will be returned directly
		String str3 = "ab";
		//Because the heap memory and "ab" in the string pool are the same object, the following two statements Print true
		System.out.println(str == st2);
		System.out.println(str == str3);
	}
}
 

Example 2

public class Main {
	public static void main(String[] args) {
        //The string object "ab" is created here. Because there is no "ab" in the string pool, it is put into the string pool
		String str3 = "ab";
        //"A" and "B" are put into the string pool, and str exists in heap memory
		String str = new String("a") + new String("b");
        //At this time, because "ab" already exists in the string pool when creating str3, putting in fails, but "ab" in the string pool will be returned
		String str2 = str.intern();
        //false
		System.out.println(str == str2);
        //false
		System.out.println(str == str3);
        //true
		System.out.println(str2 == str3);
	}
}
 

intern method 1.6

Calling the intern method of the string object will try to put the string object into the string pool

  • If there is no string object in the string pool, a copy of the string object will be copied and put into the string pool
  • If there is this string object, it fails to be put in

The string object in the string pool will be returned no matter whether the insertion is successful or not

Note: at this time, the string object in the string pool and the string object in the heap memory are not the same object regardless of whether the intern method is called successfully or not

StringTable garbage collection

When the memory of StringTable is tight, garbage collection will occur

StringTable tuning

  • Because StringTable is implemented by HashTable, the number of HashTable buckets can be appropriately increased to reduce the time required for strings to be put into the string pool

    -XX:StringTableSize=xxxx
    

  • Consider whether you need to pool string objects

    You can use the intern method to reduce repeated pooling

6. Direct memory

  • It belongs to the operating system and is commonly used in NIO operation. It is used for data buffer
  • Allocation recovery cost is high, but read-write performance is high
  • Not managed by JVM memory reclamation

Document reading and writing process

DirectBuffer used

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

Release principle

Direct memory recycling is not released through JVM garbage collection, but through unsafe Freememory to manually release

adopt

//Request 1M of direct memory through ByteBuffer
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1M);

Apply for direct memory, but the JVM cannot recycle the contents of direct memory. How does it recycle?

Implementation of allocateDirect

public static ByteBuffer allocateDirect(int capacity) {
    return new DirectByteBuffer(capacity);
}

DirectByteBuffer class

DirectByteBuffer(int cap) {   // package-private
   
    super(-1, 0, cap, cap);
    boolean pa = VM.isDirectMemoryPageAligned();
    int ps = Bits.pageSize();
    long size = Math.max(1L, (long)cap + (pa ? ps : 0));
    Bits.reserveMemory(size, cap);

    long base = 0;
    try {
        base = unsafe.allocateMemory(size); //Request memory
    } catch (OutOfMemoryError x) {
        Bits.unreserveMemory(size, cap);
        throw x;
    }
    unsafe.setMemory(base, size, (byte) 0);
    if (pa && (base % ps != 0)) {
        // Round up to page boundary
        address = base + ps - (base & (ps - 1));
    } else {
        address = base;
    }
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); //Release the direct memory through virtual reference. this is the actual object of virtual reference
    att = null;
}

Here, a Cleaner's create method is called, and the background thread will also monitor the virtual referenced objects. If the virtual referenced actual objects (DirectByteBuffer in this case) are recycled, Cleaner's clean method will be called to clear the memory occupied in the direct memory

public void clean() {
       if (remove(this)) {
           try {
               this.thunk.run(); //Call the run method
           } catch (final Throwable var2) {
               AccessController.doPrivileged(new PrivilegedAction<Void>() {
                   public Void run() {
                       if (System.err != null) {
                           (new Error("Cleaner terminated abnormally", var2)).printStackTrace();
                       }

                       System.exit(1);
                       return null;
                   }
               });
           }

run method of corresponding object

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

Summary of direct memory recycling mechanism

  • The Unsafe class is used to complete the allocation and recycling of direct memory. The recycling needs to actively call the freeMemory method
  • The implementation of ByteBuffer internally uses Cleaner (Virtual Reference) to detect ByteBuffer. Once ByteBuffer is garbage collected, ReferenceHandler will call Cleaner's clean method and freeMemory to free memory

3, Garbage recycling

Topics: Java jvm Back-end