There have been several generations of collectors, including Serial, ParNew, Parallel, CMS, G1, and the latest ZGC in Java11. Each generation of GC makes a significant improvement over the problems of the previous generation.

Today we introduce an antique collector -Serial Serial GC.

Although this collector is no longer used in many scenarios, this article uses this collector to explain how to allocate the size of each chunk of heap memory and details the GC process of Serial GC in both the new and old generations, based on GC logs.

Serial GC’s name pretty much sums it up: Serial. It executes serially with the application thread, that is, when the application thread executes, no GC is executed, and when the application thread executes, no GC is executed.

So, the entire Java process should run like this:

The Serial GC uses a generational algorithm. In the new generation, Serial used a copy algorithm for collection, and in the old generation, Serial used a mark-compression algorithm for collection.

Generation algorithm, copy algorithm, mark-compression please move:

Java Virtual Machine -GC Garbage Collection Algorithm – Tag cleanup, copy algorithm, tag compression, generation algorithm

1 Problems in Serial

As shown in The figure above, The GC thread blocks all user threads (stop-the-world, or STW) when it needs to perform GC, and does not resume The user thread until it is finished.

This resulted in varying degrees of lag for our application every GC, which was extremely user unfriendly.

2 Application Scenarios

Personal opinion:

First, according to its characteristics, the recovery algorithm is simple, so the recovery efficiency is high.

Second, it is a single-thread collection and there is no switching between GC threads. Since Java thread switching is scheduled by the system kernel, the system overhead caused by scheduling can be greatly reduced under single thread.

So there may be some use for single-core CPU machines where the business scenario is for internal use only and you can tolerate STW lag.

3 of actual combat

Environment:

  • CPU: I7 4 cores
  • Memory: 16 g
  • JDK version: 8

3.1 Let’s take a look at which GC is used by default

Add the following JVM parameters and run the code to observe the GC log

** * JVM parameters: * -xx :+PrintGCDetails -xx :+PrintGCTimeStamps */
public static void main(String[] args) {
    System.out.println("Hello SerialGC");
}
Copy the code

The program output is as follows

Hello SerialGC
// Here is the GC log
Heap
PSYoungGen      total 76288K, used 6554K [0x000000076b180000.0x0000000770680000.0x00000007c0000000)
  eden space 65536K, 10% used [0x000000076b180000.0x000000076b7e6930.0x000000076f180000)
  from space 10752K, 0% used [0x000000076fc00000.0x000000076fc00000.0x0000000770680000)
  to   space 10752K, 0% used [0x000000076f180000.0x000000076f180000.0x000000076fc00000)
ParOldGen       total 175104K, used 0K [0x00000006c1400000.0x00000006cbf00000.0x000000076b180000)
  object space 175104K, 0% used [0x00000006c1400000.0x00000006c1400000.0x00000006cbf00000)
Metaspace       used 3458K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 381K.capacity 388K.committed 512K.reserved 1048576K
Copy the code
  • PSYoungGen: Indicates that the young generation is using ParallelGC
  • ParOldGen: Indicates that ParallelGC is used in older generations
  • Metaspace: Metadata area usage

As you can see, ParallelGC, which supports multithreaded concurrency, is the default JVM choice for multi-core scenarios.

3.2 Is the Serial GC the default collector running in Client mode?

As mentioned in Zhou zhiming’s book, SerialGC is still the default collector in -client mode.

Let’s experiment with the JVM startup parameter plus the -client parameter

/** * JVM parameters: * -client-xx :+PrintGCDetails -xx :+PrintGCTimeStamps */
public static void main(String[] args) {
    System.out.println("Hello SerialGC");
}
Copy the code

The running results are as follows:

Hello SerialGC in client mode
Heap
PSYoungGen      total 76288K, used 6554K [0x000000076b180000.0x0000000770680000.0x00000007c0000000)
  eden space 65536K, 10% used [0x000000076b180000.0x000000076b7e6930.0x000000076f180000)
  from space 10752K, 0% used [0x000000076fc00000.0x000000076fc00000.0x0000000770680000)
  to   space 10752K, 0% used [0x000000076f180000.0x000000076f180000.0x000000076fc00000)
ParOldGen       total 175104K, used 0K [0x00000006c1400000.0x00000006cbf00000.0x000000076b180000)
  object space 175104K, 0% used [0x00000006c1400000.0x00000006c1400000.0x00000006cbf00000)
Metaspace       used 3513K, capacity 4498K, committed 4864K, reserved 1056768K
  class space    used 387K.capacity 390K.committed 512K.reserved 1048576K
Copy the code

ParallelGC is still visible.

This is probably because, under JDK1.8, both -client and -server parameters are invalid by default, so specifying -client does not help.

In fact, I have tried JDK6 and JDK7 in the same environment, but SerialGC is also not SerialGC, so I suspect that the JVM will choose SerialGC by default in the case of older single-core CPUS, but I have not verified this.

PS: -client and -server

The -client and -server arguments were used in previous JDK versions to select the compiler to use during JVM running. For programs requiring startup performance, -client can be used, corresponding to the compiler C1 with faster compilation efficiency; for programs requiring peak performance, -server can be used, corresponding to C2 with faster generated code execution efficiency (refer to the course introduced by Zheng Yudi in Geek time).

Java8 uses hierarchical compilation by default, which automatically selects which compiler to use when, so client and server parameters are invalidated by default. Compared to previous JDK versions, this mechanism greatly improves the efficiency of code compilation and execution.

3.3 Serial GC Field – JVM parameters

This section explains how to configure the size of each chunk of heap memory.

First we need to specify which blocks of memory we need to specify. Because Serial GC is generational collection, it is necessary to confirm the size of the Cenozoic and the old age, and the size of the Eden region and Survivor region for the Cenozoic.

  • Defines the size of the entire heap memory
// -xmx: maximum heap memory, -xms: minimum heap memory
-Xmx200M -Xms200M
Copy the code
  • Define the size of Cenozoic and old age
// NewRatio indicates the ratio of the old to the new. 3 indicates 3:1
// Divide the whole heap into 4 parts, 3 parts for the old generation and 1 part for the new generation
// The current heap memory is 200M, NewRatio=3, new generation =50M, old age =150M
-XX:NewRatio=3
Copy the code
  • Define the size of Eden and Survivor zones
// SurvivorRatio indicates the ratio of Eden and two Survivor zones. 3 indicates 3:2.
// That is, the New generation is divided into 5 groups, including 3 Eden groups and 2 Survivor groups
// If Survivor=3, Eden=30M, Survivor=20M (from=10M, to=10M)
-XX:SurvivorRatio=3
Copy the code
  • Configure GC log printing parameters
// -xx :+UseSerialGC: displays the specified use of SerialGC
// -xx :+PrintGCDetails: Prints the GC details log
// -xx :+PrintGCTimeStamps: prints the GC time
-XX:+UseSerialGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
Copy the code
  • practice

Using the same Hello SerialGC program as above, the result is as follows

Hello SerialGC
Heap
// def new generation: SerialGC, total:40M, used: 4302K
// Total 10M less? This is because the new generation uses the replication algorithm. In fact, only one From area and to area can be used each time, so it is Eden's 30M + From area or 10M = 40M of TO
def new generation   total 40960K, used 4302K [0x00000000f3800000.0x00000000f6a00000.0x00000000f6a00000)
  / / Eden area 30 m
  eden space 30720K,  14% used [0x00000000f3800000.0x00000000f3c33b78.0x00000000f5600000)
  / / the from area 10 m
  from space 10240K,   0% used [0x00000000f5600000.0x00000000f5600000.0x00000000f6000000)
  / / to 10 m
  to   space 10240K,   0% used [0x00000000f6000000.0x00000000f6000000.0x00000000f6a00000)
// SerialGC is used in the old age. The total size is 150M
tenured generation   total 153600K, used 0K [0x00000000f6a00000.0x0000000100000000.0x0000000100000000)
   the space 153600K,   0% used [0x00000000f6a00000.0x00000000f6a00000.0x00000000f6a00200.0x0000000100000000)
// The size of the metadata area is not concerned
Metaspace       used 3450K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 380K.capacity 388K.committed 512K.reserved 1048576K
Copy the code

For the copy algorithm, please go to:

Replication algorithm

3.4 Serial GC Combat – Use GC logs to understand the GC process of the new generation and the old age

This experiment was run under the JVM parameter configuration conditions described above.

Let’s take a look at this with an example program

public class SerialGCDemo {

    /** * heap memory: -xmx200m -xms200m * xm: -xx :NewRatio=3 -xx :SurvivorRatio=3 * GC -xx :+UseSerialGC -xx :+PrintGCTimeStamps -xx :+PrintGCTimeStamps 10M, New generation to area: 10M * -Xmx200M -Xms200M -XX:NewRatio=3 -XX:SurvivorRatio=3 -XX:+UseSerialGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps *@param args
     */
    public static void main(String[] args) {
        byte[][] useMemory = new byte[1000] []; Random random =new Random();
        for (int i = 0; i < useMemory.length; i++) {
            useMemory[i] = new byte[1024 * 1024 * 10]; // Create a 10M object
            // There is a 20% chance that the created object will become recyclable
            if (random.nextInt(100) < 20) {
                System.out.println("created byte[] and set to null: " + i);
                useMemory[i] = null;
            } else {
                System.out.println("created byte[]: "+ i); }}}}Copy the code

The overall log input is as follows:

created byte[] :0
created byte[] :1
0.236: [GC (Allocation Failure) 0.236: [DefNew: 24807K->870K(40960K), 0.0132148 secs] 24807K->21350K(194560K), 0.0132618 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
created byte[] :2
created byte[] and set to null: 3
0.252: [GC (Allocation Failure) 0.252: [DefNew: 21941K->717K(40960K), 0.0060942 secs] 42421K->31437K(194560K), 0.0061231 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
created byte[] :4
created byte[] :5
0.259: [GC (Allocation Failure) 0.259: [DefNew: 22408K->717K(40960K), 0.0114560 secs] 53128K->51917K(194560K), 0.0114856 secs] [Times: user=0.00 sys=0.02, real=0.02 secs]
created byte[] :6
created byte[] :7
0.285: [GC (Allocation Failure) 0.285: [DefNew: 21788K->717K(40960K), 0.0122524 secs] 72988K->72397K(194560K), 0.0122868 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
created byte[] :8
created byte[] :9
0.299: [GC (Allocation Failure) 0.299: [DefNew: 21790K->717K(40960K), 0.0115042 secs] 93470K->92877K(194560K), 0.0115397 secs] [Times: user=0.03 sys=0.00, real=0.02 secs]
created byte[] :10
created byte[] :11
0.312: [GC (Allocation Failure) 0.312: [DefNew: 21791K->717K(40960K), 0.0120174 secs] 113952K->113357K(194560K), 0.0120525 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
created byte[] :12
created byte[] :13
0.328: [GC (Allocation Failure) 0.328: [DefNew: 21792K->717K(40960K), 0.0162437 secs] 134432K->133837K(194560K), 0.0162844 secs] [Times: user=0.00 sys=0.01, real=0.02 secs]
created byte[] :14
created byte[] :15
0.347: [GC (Allocation Failure) 0.347: [DefNew: 21793K->21793K(40960K), 0.0000201 secs]0.347: [Tenured: 133120K->143360K(153600K), 0.0103885 secs] 154913K->154316K(194560K), [Metaspace: 3350K->3350K(1056768K)], 0.0104608 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
Exception in thread "main" created byte[] :16
0.361: [Full GC (Allocation Failure)0.361: [Tenured: 143360 k - > 143360K(153600K), 0.0028089 secs] 165153 - > 164556 kK(194560K), [Metaspace: 3350K->3350K(1056768K)], 0.0028543 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.364: [Full GC (Allocation Failure)0.364: [Tenured: 143360 k - > 143360K(153600K), 0.0050038 secs] 164556 - > 164538 kK(194560K), [Metaspace: 3350K->3350K(1056768K)Secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Disconnected from the target VM, address: '127.0.0.1:57881', transport: 'socket'
java.lang.OutOfMemoryError: Java heap space
Heap
    at com.example.demo.gcdemo.SerialGCDemo.main(SerialGCDemo.java:28)
def new generation   total 40960K, used 22281K [0x00000000f3800000.0x00000000f6a00000.0x00000000f6a00000)
  eden space 30720K,  72% used [0x00000000f3800000.0x00000000f4dc27c0.0x00000000f5600000)
  from space 10240K,   0% used [0x00000000f6000000.0x00000000f6000000.0x00000000f6a00000)
  to   space 10240K,   0% used [0x00000000f5600000.0x00000000f5600000.0x00000000f6000000)
tenured generation   total 153600K, used 143360K [0x00000000f6a00000.0x0000000100000000.0x0000000100000000)
   the space 153600K,  93% used [0x00000000f6a00000.0x00000000ff6000e0.0x00000000ff600200.0x0000000100000000)
Metaspace       used 3381K, capacity 4568K, committed 4864K, reserved 1056768K
  class space    used 364K.capacity 392K.committed 512K.reserved 1048576K
Copy the code

Log description:

0.236: [GC (Allocation Failure) 0.236: [DefNew: 24807K->870K(40960K), 0.0132148 secs] 24807K->21350K(194560K), 0.0132618 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
Copy the code
  • 0.236: Time of GC occurrence (in seconds), calculated from the start of the program
  • [GC: The GC type, and also the Full GC, and the Full GC both cause STW.
  • (Allocation Failure) : Memory Allocation fails due to GC
  • [DefNew: Indicates that the new generation is collected by Serail GC, i.e. default new generation.
  • 24087K -> 870K(40960K) : Used memory capacity before GC -> Used memory capacity after GC (total memory capacity of the region)
  • 0.0132148 SECS: Time for GC of this memory region (s)
  • 24807K->21350K(194560K) : Used heap memory before GC -> used heap memory after GC
  • 0.0132618 SECS: Total reclamation time (s)
  • [Times: user=0.02 sys=0.00, real= 0.01secs] : User: CPU time consumed in user mode, sys: CPU time consumed in kernel mode, real: wall clock time from the start to the end of an operation.
0.361: [Full GC (Allocation Failure)0.361: [Tenured: 143360 k - > 143360K(153600K), 0.0028089 secs] 165153 - > 164556 kK(194560K), [Metaspace: 3350K->3350K(1056768K)], 0.0028543 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Copy the code

Here is only to illustrate the differences with the above

  • [Full GC: Indicates the GC type, causing STW
  • 10. Tenured: Recycled from old age
  • 143360K->143360K(153600K) : Used memory capacity before GC in old age -> Used memory capacity after GC in old age (total capacity in old age)
  • 165153K->164556K(194560K) : Heap memory used before GC -> Heap memory used after GC (total heap memory)
  • Metaspace: Metadata area memory reclamation

The following steps take a detailed look at the memory changes from start to finish

The initial state of the entire memory is as follows:

created byte[] :0
created byte[] :1
0.236: [GC (Allocation Failure) 0.236: [DefNew: 24807K->870K(40960K), 0.0132148 secs] 24807K->21350K(194560K), 0.0132618 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
Copy the code

Two 10M objects (denoted as ID: 0 and ID: 1) are created and not set as recyclable objects. Since there is at least one Random object in Eden area at present, when applying for memory for the third object, IT is found that Eden area has insufficient memory and GC is triggered.

The new generation changes to 870K after GC, indicating that the Random object is copied to the FROM area, while the two 10M objects are directly promoted to the old age.

created byte[] :2
created byte[] and set to null: 3
0.252: [GC (Allocation Failure) 0.252: [DefNew: 21941K->717K(40960K), 0.0060942 secs] 42421K->31437K(194560K), 0.0061231 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Copy the code

Create objects ID: 2 and ID: 3 and set ID: 3 to recyclable

GC will try to copy objects in Eden area and from area to area. Objects with ID: 3 will be recycled directly (it can be seen from the capacity change of heap space: 42421K->31437K). Objects with ID: 2 will not be placed in TO area and will be promoted to the old age

Until the creation of ID: 12, ID: 13, the process is similar to the above, and no garbage objects are generated, but after the creation of ID: 13, the used memory of the old age reaches 130M+, as follows:

created byte[] :12
created byte[] :13
0.328: [GC (Allocation Failure) 0.328: [DefNew: 21792K->717K(40960K), 0.0162437 secs] 134432K->133837K(194560K), 0.0162844 secs] [Times: user=0.00 sys=0.01, real=0.02 secs]
Copy the code

After creating objects with ID: 14 and ID: 15, the next generation GC is needed

created byte[] :14
created byte[] :15
0.347: [GC (Allocation Failure) 0.347: [DefNew: 21793K->21793K(40960K), 0.0000201 secs]0.347: [Tenured: 133120K->143360K(153600K), 0.0103885 secs] 154913K->154316K(194560K), [Metaspace: 3350K->3350K(1056768K)], 0.0104608 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
Copy the code

The following information is displayed before GC

In the new generation GC, objects with ID: 14 and ID: 15 are copied to the old generation, but the old generation is no longer large enough to hold the two objects, and the old generation GC is triggered.

The Tenured portion of the log. But found that there were no objects to recycle, and then tried to copy an object in Eden to the old age

It then continues to create objects, and continues to try Full GC, Full GC fails, and eventually runs out of memory.

Exception in thread "main" created byte[] :16
0.361: [Full GC (Allocation Failure)0.361: [Tenured: 143360 k - > 143360K(153600K), 0.0028089 secs] 165153 - > 164556 kK(194560K), [Metaspace: 3350K->3350K(1056768K)], 0.0028543 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.364: [Full GC (Allocation Failure)0.364: [Tenured: 143360 k - > 143360K(153600K), 0.0050038 secs] 164556 - > 164538 kK(194560K), [Metaspace: 3350K->3350K(1056768K)Secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Disconnected from the target VM, address: '127.0.0.1:57881', transport: 'socket'
java.lang.OutOfMemoryError: Java heap space
Heap
    at com.example.demo.gcdemo.SerialGCDemo.main(SerialGCDemo.java:28)
Copy the code

4 summarizes

SerialGC is a Serial collector, which generates STW during collection and has a long pause time, resulting in poor user experience.

We then went through the field and showed you how to specify each chunk of JVM heap memory.

Finally, a case is given to describe the whole process of SerialGC and memory changes in detail.