Garbage collection in JVM

stay Last In, we have learned about the partitions and functions of the JVM. This article will introduce the GC that acts on the HeapSpace and MetaSpace regions.

GC overview

   from the birth of JVM to now, no matter what collector is, it is based on the combination or application of three basic GC algorithms. Up to now, the collector has been divided into two groups: generational collection and partitioned collection. Both of them have only one purpose to improve the throughput of the JVM and reduce the application pause time.

Garbage identification

1. Reference count

   count the reference of each object. Whenever there is a place to reference it, the counter is + 1. If the reference fails, - 1. The reference count is placed in the object header. Objects greater than 0 are considered as living objects.

   although the problem of circular reference can be solved by the Recycler algorithm, in the multithreaded environment, the reference count change also needs expensive synchronization operation, and the performance is low. This algorithm will be adopted by early programming languages.

2. Reachability algorithm

   start the object search from GC Root. The objects that can be searched are reachable objects. At this time, it is not enough to judge whether the objects live / die. It needs to be marked for many times to determine more accurately, and the objects outside the whole connected graph can be collected as garbage. At present, the mainstream virtual machines in Java adopt this algorithm.

In Java, there are four kinds of objects that can be used as GC Roots:

  • Objects referenced in the virtual machine stack (the local variable table in the frame stack allocated to the method)
  • Object referenced by static attribute in method area
  • Object referenced by constant in method area
  • The object referenced by the local method stack JNI (native method)

Three garbage collection algorithms

  • The first step is to clear the mark root mark of the object that is not marked by GC. It will cause the problem of memory fragmentation, which is easy to occur when storing large objects.

  • Copying: copy the living objects From the From area To the unused To area. There are problems such as low space utilization and high cost when copying large objects.

  • Mark compact: the first step is to mark the living objects through GC Root and sort the living objects according to the comparison order. The cost of object movement

By default, the space ratio of the new generation to the old generation in the heap is 1:2

New generation replication algorithm:

Memory space ratio: Eden: Survivor From: Survivor To=8:1:1

  1. When the Eden area is full, the first Minor gc will be triggered To copy the living objects To the Survivor From area; When the miner GC is triggered for the second time when the Eden area is full, the Eden area and the From area will be scanned for garbage collection. The objects that survive this collection will be directly copied To the To area and the Eden and From areas will be cleared

  2. When the third Minor GC occurs when the subsequent Eden is full, the Eden and To areas will be garbage collected, the surviving objects will be copied To the From area, and the Eden and To areas will be emptied.

  3. Some objects will be copied back and forth in the From and To areas and exchanged for 15 times (determined by the JVM parameter MaxTenuringThreshold, which is 15 by default). Finally, if they still survive, they will be saved To the old age.

Marking sorting algorithm of old age:

For the object recycling with high survival rate of the elderly generation, the tag collation algorithm is used to move all the surviving objects to one end, and then directly clean up the memory outside the end boundary

The GC that occurred in the old age is called Full GC, also known as Major GC, which is often accompanied by Minor GC at least once (not absolutely, there is a direct policy selection process of Full GC in the Parallel Scavenge collector). The speed of Major GC is generally more than 10 times slower than Minor GC, so Full GC should be avoided as far as possible.

garbage collector

Garbage collector is the specific implementation of garbage collection algorithm. Common garbage collectors are as follows:

  • Serial: serial collection. Using one thread for garbage collection will suspend the user thread executing in the JVM and cannot provide external services
  • Parallel: parallel collection (JDK 8 default). Using multiple threads for garbage collection will also pause the user threads executing in the JVM and shorten the time of external service suspension
  • CMS (Concurrent Mark Sweep): concurrent collection. User threads and GC threads can run at the same time, and the service will not be suspended externally
  • G1 (Garbage First): divide the heap memory into different areas, and garbage collect each area concurrently. It can be used from JDK 8. JDK 9 uses this method by default.

So how do I view the garbage collector currently used by the JVM?

Use the following command on the console to view the parameters of the JVM configuration:

>java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=132413952 -XX:MaxHeapSize=2118623232 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)

The above are some default parameters under JDK 8- 20: + useparallelgc, "+" indicates that parallel collection is activated.
There are 6 default garbage collection types in JDK 8:

  • UseSerialGC
  • UseParallelNewGC
  • UseParallelGC
  • UseParallelOldGC
  • UseConcMarkSweepGC
  • UseG1GC

If you want to modify the garbage collector, you can use - XX:+UseG1GC in the VM startup parameter to modify it to the required collector.

So how to check whether a jar uses UseG1GC?

F:\IDEA_Project\springBoot\SpringBootDemo>jps #Viewing processes in the jvm
38224 Jps
39136 Launcher
28644
38984 Main1

F:\IDEA_Project\springBoot\SpringBootDemo>jinfo -flag UseG1GC 38984
-XX:+UseG1GC 			#"+" indicates that Main1 uses the UseG1GC collector

F:\IDEA_Project\springBoot\SpringBootDemo>jinfo -flag UseParallelGC 38984
-XX:-UseParallelGC		#"-" indicates that Main1 does not use UseParallelGC collector

What scope are the above garbage collectors applied to?

The garbage collectors implemented in the new generation include UseSerialGC, UseParallelGC and UseParNewGC

Garbage collectors in the old days include UseSerialOldGC, useparalleloldgc and UseConcMarkSweepGC

The garbage collectors that can be implemented in the new generation and the old generation are: UseG1GC

Code use case of garbage collector (environment: JDK 8)

Code for testing, relevant JVM parameters:

Parameters:
-Xms10m -Xmx10m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+(Type of garbage collector)
code:
	public static void main(String[] args) {
        System.out.println("hi serial GC");
        //1MB
        int size=1*1024*1024;
        byte[][] bytes=new byte[1024][];
        for (int i = 0; i < 20; i++) {
            bytes[i]=new byte[size];
        }
    }

Next, set different garbage collectors for this code to test and observe the GC log.

Serial + SerialOld

   the Serial collector is characterized by stability. It is a single threaded garbage collection collector. It will execute STW (stop the word) to pause all other threads and wait for the garbage collection to end.

   when we use the parameter - XX:+UseSerialGC, we tell the JVM to use Serial mode for GC in Yong Generation (using replication algorithm) and Serial Old mode for GC in Tenured Generation (using tag sorting algorithm).

The following garbage collection log is the UseSerialGC used.

-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseSerialGC 
[GC (Allocation Failure) [DefNew: 2752K->320K(3072K), 0.0023756 secs] 2752K->1179K(9920K), 0.0024418 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 3072K->319K(3072K), 0.0022960 secs] 3931K->1824K(9920K), 0.0023400 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
hi serial GC
[GC (Allocation Failure) [DefNew: 2624K->50K(3072K), 0.0023840 secs] 4129K->3920K(9920K), 0.0024102 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 2150K->49K(3072K), 0.0014084 secs] 6020K->5967K(9920K), 0.0014271 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 2141K->2141K(3072K), 0.0000138 secs][Tenured: 5918K->5918K(6848K), 0.0028538 secs] 8059K->8015K(9920K), [Metaspace: 3547K->3547K(1056768K)], 0.0029293 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [Tenured: 5918K->5887K(6848K), 0.0035693 secs] 8015K->7983K(9920K), [Metaspace: 3547K->3547K(1056768K)], 0.0036204 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 3072K, used 2253K [0x00000000ff600000, 0x00000000ff950000, 0x00000000ff950000)
  eden space 2752K,  81% used [0x00000000ff600000, 0x00000000ff833790, 0x00000000ff8b0000)
  from space 320K,   0% used [0x00000000ff8b0000, 0x00000000ff8b0000, 0x00000000ff900000)
  to   space 320K,   0% used [0x00000000ff900000, 0x00000000ff900000, 0x00000000ff950000)
 tenured generation   total 6848K, used 5887K [0x00000000ff950000, 0x0000000100000000, 0x0000000100000000)
   the space 6848K,  85% used [0x00000000ff950000, 0x00000000fff0fed8, 0x00000000fff10000, 0x0000000100000000)
 Metaspace       used 3584K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 389K, capacity 392K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at JVM.GarbageCollection.main(GarbageCollection.java:19)

ParNew + SerialOld

   ParNew is a parallel multi thread garbage collector. It will execute STW (stop the word) to pause all other threads and wait for the garbage collection to end.

   when we use the parameter - XX:+UseParNewGC, we tell the JVM to use Parallel mode for GC in Yong Generation (using replication algorithm) and Serial Old mode for GC in Tenured Generation (using tag sorting algorithm).

Available parameters:
   - XX:ParallelGCThreads=Num: configure the number of threads of the parallel collector, that is, how many threads are garbage collected at the same time. The default value is the number of processors.

The following garbage collection log uses UseParNewGC.
The difference between this method and UseSerialGC is that the number of threads during garbage collection is inconsistent. It is worth noting that the prompt at the end of the log tells that the collection of this method is marked as degraded in JDK 8 and later.

-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC 
[GC (Allocation Failure) [ParNew: 2752K->319K(3072K), 0.0009978 secs] 2752K->1204K(9920K), 0.0010476 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 3071K->320K(3072K), 0.0010187 secs] 3956K->1876K(9920K), 0.0010440 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
hi serial GC
[GC (Allocation Failure) [ParNew: 2564K->291K(3072K), 0.0016276 secs] 4120K->4214K(9920K), 0.0016511 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 2462K->75K(3072K), 0.0012649 secs] 6385K->6046K(9920K), 0.0012911 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 2180K->2180K(3072K), 0.0000129 secs][Tenured: 5970K->6656K(6848K), 0.0040956 secs] 8151K->7699K(9920K), [Metaspace: 3491K->3491K(1056768K)], 0.0041689 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC (Allocation Failure) [Tenured: 6656K->6656K(6848K), 0.0023813 secs] 8723K->8723K(9920K), [Metaspace: 3491K->3491K(1056768K)], 0.0024138 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [Tenured: 6656K->6560K(6848K), 0.0033044 secs] 8723K->8627K(9920K), [Metaspace: 3491K->3491K(1056768K)], 0.0033449 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 par new generation   total 3072K, used 2226K [0x00000000ff600000, 0x00000000ff950000, 0x00000000ff950000)
  eden space 2752K,  80% used [0x00000000ff600000, 0x00000000ff82cab0, 0x00000000ff8b0000)
  from space 320K,   0% used [0x00000000ff8b0000, 0x00000000ff8b0000, 0x00000000ff900000)
  to   space 320K,   0% used [0x00000000ff900000, 0x00000000ff900000, 0x00000000ff950000)
 tenured generation   total 6848K, used 6560K [0x00000000ff950000, 0x0000000100000000, 0x0000000100000000)
   the space 6848K,  95% used [0x00000000ff950000, 0x00000000fffb8390, 0x00000000fffb8400, 0x0000000100000000)
 Metaspace       used 3572K, capacity 4498K, committed 4864K, reserved 1056768K
  class space    used 387K, capacity 390K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at JVM.GarbageCollection.main(GarbageCollection.java:16)
Java HotSpot(TM) 64-Bit Server VM warning: Using the ParNew young collector with the Serial old collector is deprecated and will likely be removed in a future release

Parallel (Parallel Scanvenge) + parallel old: (8 default combinations of JDK)

   Parallel is a Parallel garbage collector with multiple threads. It will execute STW (stop the word) to pause all other threads and wait for the garbage collection to end.

   Parallel and ParNew are both multi-threaded collectors, but Parallel has an adaptive adjustment strategy (adaptive adjustment strategy: dynamically adjust the pause time of GC according to system performance)

   when we use the parameter - XX:+UseParallelGC or - XX:+UseParallelOldGC, we tell the JVM to use Parallel Scavenge for GC in Yong Generation (using replication algorithm), and parallel old for GC in Tenured Generation (using tag collation algorithm).

-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 
[GC (Allocation Failure) [PSYoungGen: 2048K->496K(2560K)] 2048K->971K(9728K), 0.0012240 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 2544K->480K(2560K)] 3019K->1595K(9728K), 0.0013929 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
hi serial GC
[GC (Allocation Failure) [PSYoungGen: 2120K->480K(2560K)] 8356K->7058K(9728K), 0.0009738 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 480K->0K(2560K)] [ParOldGen: 6578K->6881K(7168K)] 7058K->6881K(9728K), [Metaspace: 3542K->3542K(1056768K)], 0.0113635 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1088K->1024K(2560K)] [ParOldGen: 6881K->6427K(7168K)] 7970K->7451K(9728K), [Metaspace: 3543K->3543K(1056768K)], 0.0110307 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) --[PSYoungGen: 1024K->1024K(2560K)] 7451K->7451K(9728K), 0.0005160 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 1024K->1024K(2560K)] [ParOldGen: 6427K->6406K(7168K)] 7451K->7431K(9728K), [Metaspace: 3543K->3543K(1056768K)], 0.0104724 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 2560K, used 1117K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  eden space 2048K, 54% used [0x00000000ffd00000,0x00000000ffe174a8,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
 ParOldGen       total 7168K, used 6406K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
  object space 7168K, 89% used [0x00000000ff600000,0x00000000ffc41bd8,0x00000000ffd00000)
 Metaspace       used 3585K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 389K, capacity 392K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at JVM.GarbageCollection.main(GarbageCollection.java:16)

ParNew + CMS(Concurrent Mark Sweep)

   Concurrent Mark Sweep concurrent mark sweep is a garbage collector with low pause and concurrent execution with user threads. It is suitable for the current B/S system server with large memory and many CPU cores.

   when we use the parameter - XX:+UseConcMarkSweepGC, we tell the JVM to use Parallel New method for GC in Yong Generation (using replication algorithm) and CMS method for GC in Tenured Generation (using mark clearing algorithm). If CMS fails, the JVM will execute GC in Serial Old mode.

CMS mode includes three steps:

  1. Initial Mark: quickly mark the objects that GC Roots can be directly associated with. It is necessary to pause all working threads (STW)

  2. Concurrent mark: the process of tracking GC Roots and working with user threads. Starting from the objects marked in the previous stage, all reachable objects are marked in this stage.

  3. Remark: during Concurrent Mark, the Initial Mark object will be changed due to the operation of the user thread. In this stage, the changed objects are re marked to make final preparations for formal garbage cleaning. The remark phase needs to pause all worker threads.

  4. Concurrent Sweep: clear the unreachable objects of GC Roots, work with user threads, and finally clear the marked results

-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:MaxNewSize=3497984 -XX:MaxTenuringThreshold=6 -XX:NewSize=3497984 -XX:OldPLABSize=16 -XX:OldSize=6987776 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC 
[GC (Allocation Failure) [ParNew: 2748K->320K(3072K), 0.0029262 secs] 2748K->1194K(9920K), 0.0029924 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 3072K->320K(3072K), 0.0014933 secs] 3946K->1871K(9920K), 0.0015267 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
hi serial GC
[GC (Allocation Failure) [ParNew: 2668K->271K(3072K), 0.0019613 secs] 4220K->4138K(9920K), 0.0019933 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (CMS Initial Mark) [1 CMS-initial-mark: 3867K(6848K)] 5162K(9920K), 0.0001467 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-mark-start]
[GC (Allocation Failure) [ParNew: 2371K->118K(3072K), 0.0015342 secs] 6239K->6033K(9920K), 0.0015604 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 2209K->2209K(3072K), 0.0000173 secs][CMS[CMS-concurrent-mark: 0.001/0.003 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
 (concurrent mode failure): 5915K->6616K(6848K), 0.0050711 secs] 8125K->7696K(9920K), [Metaspace: 3549K->3549K(1056768K)], 0.0051453 secs] [Times: user=0.03 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [CMS: 6616K->6400K(6848K), 0.0042853 secs] 8775K->8504K(9920K), [Metaspace: 3551K->3551K(1056768K)], 0.0043284 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [CMS: 6400K->6380K(6848K), 0.0043484 secs] 8504K->8483K(9920K), [Metaspace: 3551K->3551K(1056768K)], 0.0043880 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (CMS Initial Mark) [1 CMS-initial-mark: 6380K(6848K)] 8483K(9920K), 0.0001427 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-abortable-preclean-start]
[CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (CMS Final Remark) [YG occupancy: 2177 K (3072 K)][Rescan (parallel) , 0.0001182 secs][weak refs processing, 0.0000062 secs][class unloading, 0.0002791 secs][scrub symbol table, 0.0004622 secs][scrub string table, 0.0001111 secs][1 CMS-remark: 6380K(6848K)] 8558K(9920K), 0.0010524 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 
[CMS-concurrent-sweep-start]
[CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-reset-start]
[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 par new generation   total 3072K, used 2232K [0x00000000ff600000, 0x00000000ff950000, 0x00000000ff950000)
  eden space 2752K,  81% used [0x00000000ff600000, 0x00000000ff82e320, 0x00000000ff8b0000)
  from space 320K,   0% used [0x00000000ff8b0000, 0x00000000ff8b0000, 0x00000000ff900000)
  to   space 320K,   0% used [0x00000000ff900000, 0x00000000ff900000, 0x00000000ff950000)
 concurrent mark-sweep generation total 6848K, used 6378K [0x00000000ff950000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3587K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 389K, capacity 392K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at JVM.GarbageCollection.main(GarbageCollection.java:16)

Disadvantages:
  it must be completed in advance, otherwise it will be used

Posted by lewis987 on Thu, 05 May 2022 09:32:17 +0300