JVM garbage collector history

Parallel GC is used in JDK1.8 using jmap-heap PID

jmap -heap 18378
Attaching to process ID 18378, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.261-b12

using thread-local object allocation.
Parallel GC with 4 thread(s) ###
Copy the code

Parallel GC is nowhere to be found in the history of the JVM garbage collector, so what does it stand for?

Parallel GC comes in two combinations

  • use-XX:+UseParallelGCParameter to enableParallel ScavengeandPSMarkSweep(Serial Old)Collectors combine for garbage collection. (It can be found on the picture)
  • use-XX:+UserParallelOldGCParameter to enableParallel scavengeandParallel OldCollector group collection. (It can be found on the picture)

The Parallel origins of GC

Young GC / Parallel Scavenge

The Parallel Scavenge collector is a multithreaded collector that uses a replication algorithm, but uses a different object allocation and recycling strategy than the ParNew collector. The Parallel Avenge collector is designed to maximize the throughput of the collector by minimizing GC time. It allows STW to maximize total throughput over a longer period of time.

Full GC / PSMarkSweep(Serial Old)

The PS MarkSweep collector itself exists in the Parallel Scavenge architecture for Old age collection, but since PS MarkSweep is very close to the Serial Old implementation, So many official sources refer directly to Serial Old instead of PS MarkSweep.

Using the -xx :+UseParallelGC parameter is to turn on the PSScavenge avenge and PSMarkSweep combination, so that only the Young GC is insane and the Full GC is still serial, using the mark-collation algorithm.

Full GC / PSCompact(ParallelOld GC)

Later developers developed a parallel version of the LISP2 algorithm called the Full GC collector to collect the entire GC heap, called PSCompact. Use the -xx :+UseParallelOldGC parameter to unlock the PSScavenge and PSCompact to parallelize both the Young and Full GC.

The collector

Parallel Scavenge

New generation of parallel collector, memory distribution using copy algorithm. The Parallel Avenge is primarily concerned with the throughput of the application, while the other collectors are primarily concerned with keeping STW (Stop the word) as short as possible.

Throughput = T1 /(T1 + T2) T1 Total time to run user code T2 Total time to run garbage collection For example, if the virtual machine runs for 100 minutes and garbage collection takes 1 minute, then the throughput is 99%.Copy the code

The Parallel Scavenge collector provides two parameters to control throughput precisely, the -xx :MaxGCPauseMillis parameter, which controls the maximum garbage collection pause time, and the -xx :GCTimeRatio parameter, which controls the throughput size

  • -XX:MaxGCPauseMillis

The value of the parameter is a greater than zero, the number of milliseconds collector will as far as possible to ensure recovery time shall not exceed the set value, however, is not as small as possible, GC pause time reduced at the expense of throughput and new generation space for, if the set value is too small, will result in frequent GC, so although GC pause time down, but the throughput is also down. For example, 500MB is collected every 10 seconds, and each collection takes 100ms. If 300MB is collected every 5 seconds, each collection takes 70ms. Although each collection takes less time, the working frequency increases, resulting in reduced throughput.

  • -XX:GCTimeRatio

The value of the argument is an integer greater than 0 and less than 100, which is the ratio of garbage collection time to total time. The default value is 99, which is the maximum garbage collection time allowed (1 /(1+99)).

The Parallel Exploiture has an important feature that supports GC adaptive conditioning strategies, using -xx: UseAdaptiveSizePolicy Specifies whether to enable the UseAdaptiveSizePolicy parameter. After this parameter is enabled, VMS collect monitoring information based on the current system running status and dynamically adjust details such as the proportion of new generation and the size of old age to provide the optimal pause time or maximum throughput. After this parameter is enabled, there is no need to set the size of the new generation, the ratio of Eden to S0/S1, etc.

Parallel Old

The Parallel Old GC is responsible for the Full GC, the Parallel Avenge and the Parallel Old collector, and is a Parallel collector that uses the same conventional “clone” algorithms as the Parallel Avenge GC when collating younger generations. When it compacts, however, it uses a Mark-summary-compaction algorithm optimized from a mark-compact algorithm.

The algorithm consists of three parts

  • Mark

Firstly, the memory of the old age is divided into multiple consecutive regions with fixed sizes. After the living objects of each Region are marked, the number of living objects of each Region is counted. In the Mark phase, all objects reachable from GC Roots are sequentially marked, and then all surviving objects are marked in parallel.

  • Summary

Density of a Region = memory size of the living object/Region memory size. Each collation moves the surviving objects to the left of the Old Region, and the longer the objects live, the harder they are to be reclaimed. Therefore, after multiple collations, the objects in the left Region tend to be stable and “long-lived”, that is, the density of the left Region is higher. In Summary stage, the algorithm adopts the optimization method of space for time. For a Region with a large density, for example, 95% of the space is a viable object and only 5% of the space is intermittently unused. Then the algorithm considers that the Region is not worth sorting out, that is, it chooses to waste the 5% space. To save the time cost of the collation operation. In the Sumamry stage, the density of each Region is calculated from left to right until a point is found. The Region on the left of this point is not worth sorting out, while the Region on the right needs to be sorted out. The Region to the left of the point is called the Dense Prefix, where objects are not moved. The Summary phase is a sequential execution phase.

  • Compaction

Compaction takes statistics from the Summary phase and compacts operations in parallel using a Compaction algorithm.

GC policy

  • -XX:+ScavengeBeforeFullGC

ScavengeBeforeFullGC ScavengeBeforeFullGC is a parameter in the Parallel GC suite (both combinations work). It is turned on by default to trigger a Young GC to clean up the Young generation before a FullGC. To reduce the STW time of Full GC (Young GC cleans up non-viable objects in Young GC, reducing the amount of work to mark viable objects in Full GC).

For example, using system.gc () to trigger Full GC, you can see the following log:

The T13:2020-03-01 38:30. 496-0800: [GC (System. The GC ()) [PSYoungGen: [Times: Times: 3282K -> 3282k] 3282k -> 3282k [Times: 3282k] 3282k -> 3282k [Times: 3282k] 3282k -> 3282k [Times: 3282k] User =0.02 sys=0.01, real=0.01 secs] 2020-03-01T13:38:30.500-0800: [Full GC (system.gc ())] [PSYoungGen: 1392K->0K(46080K)] [ParOldGen: 40968K->1225K(51200K)] 42360K->1225K(97280K), [Metaspace: [Times: user= 0.06sys =0.00, real= 0.06secs]Copy the code

The first GC is a Young GC, which you can see is triggered by System.gc(), followed by a Full GC.

Adding the -xx: -scavengebeforeFullGC parameter changes the log to only one FullGC:

2020-03-01T14:26:05.562-0800: [Full GC (system.gc ())] [PSYoungGen: 37274K->0K(46080K)] [ParOldGen: 40960K->1225K(51200K)] 78234K->1225K(97280K), [Metaspace: 4882K->4882K(1056768K)], 0.0127785secs] [Times: User sys = = 0.05 0.01, real = 0.01 secs]Copy the code

Memory allocation policy

For the regular collector, a Young GC is fired when the Eden region is unable to allocate memory, but for the Parallel GC there is a change:

  • When the remaining space of the entire generation cannot store an object, the Parallel GC directly pushes the object into the old age.
  • If the remaining space of the entire generation is available but only Eden is out of space, a Minor GC will be attempted.

Here’s an example:

public class TestApp { public static void main(String[] args) throws InterruptedException { allocM(10); allocM(10); allocM(10); allocM(20); Thread.sleep(1000); } private static byte[] allocM(int n) throws InterruptedException { byte[] ret = new byte[1024 * 1024 * n]; System.out.println(String.format("%s: Alloc %dMB", LocalDateTime.now().toString(), n)); Thread.sleep(500); return ret; }}Copy the code

JVM parameters are: -Xms100m -Xmx100m -Xmn50m -XX:SurvivorRatio=8 -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -xx :+UseParallelOldGC, when running, the following logs are displayed:

2020-03-0t16:36:13.027: Alloc 1020-03-0t16:36:13.548: Alloc 1020-03-0t16:36:14.061: Alloc 10 MB T16:2020-03-01 and. 577: Alloc 20MB Heap PSYoungGen total 46080K, used 38094K [0x00000007bce00000, 0x00000007c0000000, 0x00000007c0000000) eden space 40960K, 93%, informs [x00000007bce00000 0, 0 x00000007bf333878, 0 x00000007bf600000) from space 5120 k, 0%, informs [x00000007c0000000 x00000007bfb00000 0, 0 x00000007bfb00000, 0) to space 5120 k, 0%, informs [x00000007bf600000 0, 0 x00000007bf600000, 0 x00000007bfb00000) ParOldGen total 51200 k, used 20480K [0x00000007b9c00000, 0x00000007bce00000, 0x00000007bce00000) object space 51200K, 40%, informs [x00000007b9c00000 0, 0 x00000007bb000010, 0 x00000007bce00000) Metaspace informs the 4879 k, capacity 5012 k, committed 5248K, reserved 1056768K class space used 527K, capacity 564K, committed 640K, reserved 1048576KCopy the code

As you can see in line 4, when 20 MB memory is allocated, the Eden area is already less than 20 MB free memory, so the whole Young generation is less than 20 MB free memory. But instead of triggering the Young GC, we continue to execute, and until the program ends, print the heap, we can see that 20 MB memory is allocated to the old age.

Modify the code to put the last allocM(20); To allocM (5); Run the command again, and the log is as follows:

2020-03-01T16:39:56.375: Alloc 1020-03-01T16:39:56.896: Alloc 1020-03-01T16:39:57.408: Alloc 10MB {Heap before GC invocations=1 (full 0): PSYoungGen total 46080K, used 37274K [0x00000007bce00000, 0x00000007c0000000, 0x00000007c0000000) eden space 40960K, 91%, informs [x00000007bce00000 0, 0 x00000007bf266a08, 0 x00000007bf600000) from space 5120 k, 0%, informs [x00000007c0000000 x00000007bfb00000 0, 0 x00000007bfb00000, 0) to space 5120 k, 0%, informs [x00000007bf600000 0, 0 x00000007bf600000, 0 x00000007bfb00000) ParOldGen total 51200 k, 2 0 k [0 x00000007b9c00000, 0x00000007bce00000, 0x00000007bce00000) object space 51200K, 0%, informs [x00000007b9c00000 0, 0 x00000007b9c00000, 0 x00000007bce00000) Metaspace informs the 4882 k, capacity 5012 k, committed 5248K, reserved 1056768K class space used 526K, capacity 564K, committed 640K, Reserved 1048576K 2020-03-01T16:39:57.910-0800: [GC (Allocation Failure)] [PSYoungGen: Secs] [Times: Times: 328K -> 328k] 328k -> 328k [Times: 328k] 328k -> 328k [Times: 328k] 328k -> 328k [Times: 328k] 328k -> 328k [Times: 328k] User =0.02 sys=0.00, real=0.00 secs] Heap after GC invocations=1 (full 0): PSYoungGen total 46080K, used 1328K [0x00000007bce00000, 0x00000007c0000000, 0x00000007c0000000) eden space 40960K, 0%, informs [x00000007bce00000 0, 0 x00000007bce00000, 0 x00000007bf600000) from space 5120 k, 25%, informs [x00000007bfb00000 x00000007bf600000 0, 0 x00000007bf74c010, 0) to space 5120 k, 0%, informs [x00000007bfb00000 0, 0 x00000007bfb00000, 0 x00000007c0000000) ParOldGen total 51200 k, 8 k, informs [0 x00000007b9c00000, 0x00000007bce00000, 0x00000007bce00000) object space 51200K, 0%, informs [x00000007b9c00000 0, 0 x00000007b9c02000, 0 x00000007bce00000) Metaspace informs the 4882 k, capacity 5012 k, committed 5248K, reserved 1056768K class space used 526K, capacity 564K, committed 640K, Reserved 1048576K} 2020-03-01t16:39:57.916: Alloc 5MBCopy the code

In line 4, when 5M memory is allocated, the Eden region is insufficient, but the entire Young generation has more than 5M free memory, so a Young GC is triggered.

Pessimistic strategy

Most collectors have a policy that, prior to executing the Young GC, if the average size of the previous generation is estimated to be greater than the remaining space of the current generation, the Young GC will be abandoned and the Full GC will be triggered instead.

In addition to the above strategy, the Parallel GC has another strategy: after executing the Young GC, if the average size of the older generation is greater than the remaining space of the current older generation, a Full GC will be triggered.

public class TestApp { public static void main(String[] args) throws InterruptedException { byte[][] use = new byte[7][]; use[0] = allocM(10); use[1] = allocM(10); use[2] = allocM(10); use[3] = allocM(10); use[4] = allocM(10); use[5] = allocM(10); use[6] = allocM(10); Thread.sleep(1000); } private static byte[] allocM(int n) throws InterruptedException { byte[] ret = new byte[1024 * 1024 * n]; System.out.println(String.format("%s: Alloc %dMB", LocalDateTime.now().toString(), n)); Thread.sleep(500); return ret; }}Copy the code

JVM parameters are: -Xms100m -Xmx100m -Xmn50m -XX:SurvivorRatio=8 -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -xx :+UseParallelOldGC

2020-03-01T16:02:43.172: Alloc 1020-03-01T16:02:43.693: Alloc 1020-03-01T16:02:44.206: Alloc 10MB {Heap before GC invocations=1 (full 0): PSYoungGen total 46080K, used 37274K [*, *, *) eden space 40960K, 91% used [*,*,*) from space 5120K, 0% used [*,*,*) to space 5120K, 0% used [*,*,*) ParOldGen total 51200K, used 0K [*, *, *) object space 51200K, 0% used [*,*,*) 2020-03-01T16:02:44.711-0800: [PSYoungGen: Secs] [Times: Times: 328k -> 328k] 328K -> 328k [Times: 328k] 328k -> 328k [Times: 328k] 328k -> 328k [Times: 328k] Heap after GC invocations=1 (full 0): Heap after GC invocations=1 (full 0) PSYoungGen total 46080K, used 1392K [*, *, *) eden space 40960K, 0% used [*,*,*) from space 5120K, 27% used [*,*,*) to space 5120K, 0% used [*,*,*) ParOldGen total 51200K, used 30728K [*, *, *) object space 51200K, 60% used [*,*,*) } {Heap before GC invocations=2 (full 1): PSYoungGen total 46080K, used 1392K [*, *, *) eden space 40960K, 0% used [*,*,*) from space 5120K, 27% used [*,*,*) to space 5120K, 0% used [*,*,*) ParOldGen total 51200K, used 30728K [*, *, *) object space 51200K, 60% used [*,*,*) 2020-03-01T16:02:44.728-0800: [Full GC (Ergonomics) [PSYoungGen: 1392K->0K(46080K)] [ParOldGen: 30728K->31945K(51200K)] 32120K->31945K(97280K), [Metaspace: 4881K->4881K(1056768K)], 0.0096352 secs] [Times: User =0.05 sys=0.00, real=0.01 secs] Heap after GC invocations=2 (full 1): PSYoungGen total 46080K, used 0K [*, *, *) eden space 40960K, 0% used [*,*,*) from space 5120K, 0% used [*,*,*) to space 5120K, 0% used [*,*,*) ParOldGen total 51200K, used 31945K [*, *, *) object space 51200K, 2020-03-01t16:02:44.739: Alloc 10MBCopy the code

After the first Young GC, 30M memory in Eden will be advanced to the old generation due to the insufficient capacity of s-region in the Young generation. After GC, 60% of the old decade space is occupied and 40% (20M) is left, while the average promoted memory size is 30M, so the pessimistic strategy is triggered, resulting in a Full GC.

Change -xms100m -xmx100m to -xms120m -xmx120m.

2020-03-01T16:08:39.372: Alloc 10MB 2020-03-01T16:08:39.895: Alloc 1020-03-01T16:08:40.405: Alloc 10MB {Heap before GC invocations=1 (full 0): PSYoungGen total 46080K, used 37274K [*, *, *) eden space 40960K, 91% used [*,*,*) from space 5120K, 0% used [*,*,*) to space 5120K, 0% used [*,*,*) ParOldGen total 71680K, used 0K [*, *, *) object space 71680K, 0% used [*,*,*) 2020-03-01T16:08:40.906-0800: [PSYoungGen: Secs] [Times: Times: 3305k -> 3305k] 3305k -> 3305k [Times: 3305k] 3305k -> 3305k [Times: 3305k] Heap after GC invocations=1 (full 0): Heap after GC invocations=1 (full 0) PSYoungGen total 46080K, used 1360K [*, *, *) eden space 40960K, 0% used [*,*,*) from space 5120K, 26% used [*,*,*) to space 5120K, 0% used [*,*,*) ParOldGen total 71680K, used 30728K [*, *, *) object space 71680K, 42used [*,*,*)} 2020-03-01t16:08:40.923: Alloc 2020-03-01t16:08:41.429: Alloc 2020-03-01t16:08:41.934: Alloc 10MB {Heap before GC invocations=2 (full 0): PSYoungGen total 46080K, used 32854K [*, *, *) eden space 40960K, 76% used [*,*,*) from space 5120K, 26% used [*,*,*) to space 5120K, 0% used [*,*,*) ParOldGen total 71680K, used 30728K [*, *, *) object space 71680K, 42% used [*,*,*) 2020-03-01T16:08:42.438-0800: [GC (Allocation Failure)] [PSYoungGen: 32854K->1392K(46080K)] 33582K ->62840K(117760K), 0.0151558 secs] [Times: User =0.07 sys=0.03, real=0.02 secs] Heap after GC invocations=2 (full 0): PSYoungGen total 46080K, used 1392K [*, *, *) eden space 40960K, 0% used [*,*,*) from space 5120K, 27% used [*,*,*) to space 5120K, 0% used [*,*,*) ParOldGen total 71680K, used 61448K [*, *, *) object space 71680K, 85% used [*,*,*) } {Heap before GC invocations=3 (full 1): PSYoungGen total 46080K, used 1392K [*, *, *) eden space 40960K, 0% used [*,*,*) from space 5120K, 27% used [*,*,*) to space 5120K, 0% used [*,*,*) ParOldGen total 71680K, used 61448K [*, *, *) object space 71680K, 85% used [*,*,*) Metaspace used 4883K, capacity 5012K, committed 5248K, reserved 1056768K class space used 526K, Capacity 564K, committed 640K, reserved 1048576K 2020-03-01T16:08:42.454-0800: [PSYoungGen: 1392K->0K(46080K)] [ParOldGen: 61448K->62634K(71680K)] 62840K->62634K(117760K), [Metaspace: 4883 k - > 4883 k (1056768 k)], 0.0139615 secs] [Times: Heap after GC invocations= 1 (full 1): Heap after GC invocations= 1 (full 1) PSYoungGen total 46080K, used 0K [*, *, *) eden space 40960K, 0% used [*,*,*) from space 5120K, 0% used [*,*,*) to space 5120K, 0% used [*,*,*) ParOldGen total 71680K, used 62634K [*, *, *) object space 71680K, 87%Used [*,*,*)} 2020-03-01t16:08:42.469: Alloc 10MBCopy the code

As you can see, after the first Young GC, Full GC is not triggered because there is enough free space in the old GC, and after the second Young GC, pessimistic policy is triggered as memory continues to be allocated.

Is the JVM default PSMarkSweep(serial-old) or Parallel Old?

This improvement causes HotSpot VM to turn on -xx :+UseParallelOldGC by default when it chooses to UseParallelGC (-xx :+UseParallelGC or ergonomics automatically selects). This change should take effect in JDK7u series and JDK8 series starting in JDK7u4.

Hg.openjdk.java.net/jdk8u/jdk8u…

--- a/src/share/vm/runtime/arguments.cpp Mon Jan 30 15:21:57 2012 +0100 +++ b/src/share/vm/runtime/arguments.cpp Thu Feb 02 16:05:17 @ @ 2012-0800-1400, + 1400, @ @ void the Arguments: : set_parallel_gc_flags () {assert (UseParallelGC | | UseParallelOldGC, "Error"); - // If parallel old was requested, automatically enable parallel scavenge. - if (UseParallelOldGC && ! UseParallelGC && FLAG_IS_DEFAULT(UseParallelGC)) { - FLAG_SET_DEFAULT(UseParallelGC, true); + // Enable ParallelOld unless it was explicitly disabled (cmd line or rc file). + if (FLAG_IS_DEFAULT(UseParallelOldGC)) { + FLAG_SET_DEFAULT(UseParallelOldGC, true); } + FLAG_SET_DEFAULT(UseParallelGC, true); // If no heap maximum was requested explicitly, use some reasonable fraction // of the physical memory, up to a maximum of 1GB.Copy the code

Before this change, even if ParallelGC was selected, ParallelOldGC would not be turned on by default. Instead, it would have to be selected by -xx :+UseParallelOldGC.

In the GC log, if you see “ParOldGen” in the Full GC, ParallelOldGC is selected.

[Full GC [PSYoungGen: 480K->0K(3584K)] [ParOldGen: 4660K->4909K(12288K)] 5141K->4909K(15872K) [PSPermGen: [Times: user= 0.08sys =0.00, real= 0.05secs]