JVM practice: memory allocation and recycling strategy

Posted by doni49 on Tue, 04 Jan 2022 12:54:01 +0100

In the book "in depth understanding of Java virtual machine: JVM advanced features and best practices (3rd Edition) (Huazhang original boutique) - Zhou Zhiming", in order to enhance understanding, follow the practice.

Section 3.8.1 in the book: objects are assigned in Eden first. The experimental results are as follows:

Then I got different results (I began to question the correctness of the book). Later, I thought it might be because different JDK versions use different garbage collectors. Therefore, the garbage collector - XX:+UseSerialGC is specially specified later, which is consistent with the results in the book.
In order to avoid interference from other factors, I used system Gz() forces the JVM to perform full gc once.

private static final int _1MB = 1024 *1024;
    /**
     * VM Parameters: - XX: + useserialgc - verbose: GC - xms20m - xmnx20m - xmn10m - XX: + printgcdetails - XX: survivorratio = 8
     * Note that different collector memory allocation and recycling strategies are different. This example uses - XX:+UseSerialGC. The default value of virtual machine running in Client mode is Serial+Serial Old. The results obtained are consistent with those in the book
     */
    public static void testAllocation() {
        System.gc();
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[2 * _1MB];
        allocation2 = new byte[2 * _1MB];
        allocation3 = new byte[2 * _1MB];
        allocation4 = new byte[4 * _1MB]; // Minor GC occurs once
    }

The following shows the memory allocation and recycling of each step. It can be seen that after the initial full gc is directly executed, the young generation and the old generation cannot be completely recycled, and a small part of the space will be occupied. Then allocation1 is successfully allocated 2MB in eden area, and allocation2 and allocation3 are also successfully allocated. So far, there are 6MB in eden area.

According to the parameters we set for the JVM, the heap memory is 20MB, which is not scalable (the initial memory and maximum memory are 20MB), the younger generation is 10MB (the older generation is 10MB), and the ratio of eden area to survivor area is 8 (i.e. 8:1:1, survivor is divided into from and to). Therefore, eden area has 8MB and survivor area 1M, The free space of the younger generation is 9MB (the younger generation adopts the mark copy algorithm, and the surviving objects in eden+survivor are copied to another survivor).

The younger generation has less than 2MB left, which is not enough to allocate allocation4. Therefore, GC is triggered (due to memory allocation failure). However, the current 6MB objects are still alive and cannot be recycled. They have to be transferred to the old age in advance through the allocation guarantee mechanism, so the old age becomes 6MB, and then the younger generation allocates allocation4 to 4MB.

/**
        System.gc();
//        byte[] allocation1, allocation2, allocation3, allocation4;
//        allocation1 = new byte[2 * _1MB];
//        allocation2 = new byte[2 * _1MB];
//        allocation3 = new byte[2 * _1MB];
//        allocation4 = new byte[4 * _1MB]; // Minor GC occurs once
*/
[Full GC (System.gc()) [Tenured: 0K->724K(10240K), 0.0024904 secs] 2162K->724K(19456K), [Metaspace: 3426K->3426K(1056768K)], 0.0025292 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 246K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,   3% used [0x00000000fec00000, 0x00000000fec3d890, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 724K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,   7% used [0x00000000ff600000, 0x00000000ff6b5240, 0x00000000ff6b5400, 0x0000000100000000)
 Metaspace       used 3434K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 374K, capacity 388K, committed 512K, reserved 1048576K

/**
        System.gc();
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[2 * _1MB];
//        allocation2 = new byte[2 * _1MB];
//        allocation3 = new byte[2 * _1MB];
//        allocation4 = new byte[4 * _1MB]; // Minor GC occurs once
*/
[Full GC (System.gc()) [Tenured: 0K->713K(10240K), 0.0021630 secs] 1994K->713K(19456K), [Metaspace: 3295K->3295K(1056768K)], 0.0022069 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 2458K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  30% used [0x00000000fec00000, 0x00000000fee66858, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 713K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,   6% used [0x00000000ff600000, 0x00000000ff6b2600, 0x00000000ff6b2600, 0x0000000100000000)
 Metaspace       used 3318K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 362K, capacity 388K, committed 512K, reserved 1048576K

/**
        System.gc();
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[2 * _1MB];
        allocation2 = new byte[2 * _1MB];
//        allocation3 = new byte[2 * _1MB];
//        allocation4 = new byte[4 * _1MB]; // Minor GC occurs once
*/
[Full GC (System.gc()) [Tenured: 0K->710K(10240K), 0.0018954 secs] 1994K->710K(19456K), [Metaspace: 3221K->3221K(1056768K)], 0.0019325 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 4506K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  55% used [0x00000000fec00000, 0x00000000ff066808, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 710K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,   6% used [0x00000000ff600000, 0x00000000ff6b1920, 0x00000000ff6b1a00, 0x0000000100000000)
 Metaspace       used 3363K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 369K, capacity 388K, committed 512K, reserved 1048576K

/**
        System.gc();
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[2 * _1MB];
        allocation2 = new byte[2 * _1MB];
        allocation3 = new byte[2 * _1MB];
//        allocation4 = new byte[4 * _1MB]; // Minor GC occurs once
*/
[Full GC (System.gc()) [Tenured: 0K->710K(10240K), 0.0020691 secs] 1994K->710K(19456K), [Metaspace: 3192K->3192K(1056768K)], 0.0021076 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 6554K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  80% used [0x00000000fec00000, 0x00000000ff266818, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 710K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,   6% used [0x00000000ff600000, 0x00000000ff6b1828, 0x00000000ff6b1a00, 0x0000000100000000)
 Metaspace       used 3205K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 351K, capacity 388K, committed 512K, reserved 1048576K

/**
        System.gc();
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[2 * _1MB];
        allocation2 = new byte[2 * _1MB];
        allocation3 = new byte[2 * _1MB];
        allocation4 = new byte[4 * _1MB]; // Minor GC occurs once
*/
[Full GC (System.gc()) [Tenured: 0K->724K(10240K), 0.0027738 secs] 2162K->724K(19456K), [Metaspace: 3421K->3421K(1056768K)], 0.0028359 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 6471K->25K(9216K), 0.0022628 secs] 7196K->6893K(19456K), 0.0022878 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 4203K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  51% used [0x00000000fec00000, 0x00000000ff014930, 0x00000000ff400000)
  from space 1024K,   2% used [0x00000000ff500000, 0x00000000ff5064f8, 0x00000000ff600000)
  to   space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
 tenured generation   total 10240K, used 6868K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  67% used [0x00000000ff600000, 0x00000000ffcb5138, 0x00000000ffcb5200, 0x0000000100000000)
 Metaspace       used 3447K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 376K, capacity 388K, committed 512K, reserved 1048576K

Next, verify the default garbage collector of JDK8. First, use the CMD command to see what kind of collector is used

java -XX:+PrintCommandLineFlags -version

-XX:InitialHeapSize=267464320 -XX:MaxHeapSize=4279429120 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
java version "1.8.0_171"
Java(TM) SE Runtime Environment (build 1.8.0_171-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode)

UseParallelGC, i.e. Parallel Scavenge + Parallel Old, and then view the details

java -XX:+PrintGCDetails -version

java version "1.8.0_171"
Java(TM) SE Runtime Environment (build 1.8.0_171-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode)
Heap
 PSYoungGen      total 76288K, used 2621K [0x000000076af80000, 0x0000000770480000, 0x00000007c0000000)
  eden space 65536K, 4% used [0x000000076af80000,0x000000076b20f748,0x000000076ef80000)
  from space 10752K, 0% used [0x000000076fa00000,0x000000076fa00000,0x0000000770480000)
  to   space 10752K, 0% used [0x000000076ef80000,0x000000076ef80000,0x000000076fa00000)
 ParOldGen       total 175104K, used 0K [0x00000006c0e00000, 0x00000006cb900000, 0x000000076af80000)
  object space 175104K, 0% used [0x00000006c0e00000,0x00000006c0e00000,0x00000006cb900000)
 Metaspace       used 2351K, capacity 4480K, committed 4480K, reserved 1056768K
  class space    used 254K, capacity 384K, committed 384K, reserved 1048576K

Let's change the corresponding parameter to - XX:+UseParallelGC and take a look at the results:
allocation1, allocation2 and allocation3 can be allocated directly, which is consistent with the above. The difference is that allocation4 is directly assigned to the old age (the allocation rules for large objects directly entering the old age should be used). Therefore, it can be seen that the implementation methods of different collectors are different.

/**
        System.gc();
//        byte[] allocation1, allocation2, allocation3, allocation4;
//        allocation1 = new byte[2 * _1MB];
//        allocation2 = new byte[2 * _1MB];
//        allocation3 = new byte[2 * _1MB];
//        allocation4 = new byte[4 * _1MB]; // Minor GC occurs once
*/
[GC (System.gc()) [PSYoungGen: 1994K->776K(9216K)] 1994K->784K(19456K), 0.0006403 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 776K->0K(9216K)] [ParOldGen: 8K->705K(10240K)] 784K->705K(19456K), [Metaspace: 3174K->3174K(1056768K)], 0.0031807 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 9216K, used 246K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 3% used [0x00000000ff600000,0x00000000ff63d890,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 10240K, used 705K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 6% used [0x00000000fec00000,0x00000000fecb0560,0x00000000ff600000)
 Metaspace       used 3185K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 350K, capacity 388K, committed 512K, reserved 1048576K

/**
        System.gc();
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[2 * _1MB];
//        allocation2 = new byte[2 * _1MB];
//        allocation3 = new byte[2 * _1MB];
//        allocation4 = new byte[4 * _1MB]; // Minor GC occurs once
*/
[GC (System.gc()) [PSYoungGen: 2162K->808K(9216K)] 2162K->816K(19456K), 0.0015479 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 808K->0K(9216K)] [ParOldGen: 8K->716K(10240K)] 816K->716K(19456K), [Metaspace: 3339K->3339K(1056768K)], 0.0030067 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 9216K, used 2457K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 30% used [0x00000000ff600000,0x00000000ff8667f8,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 10240K, used 716K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 7% used [0x00000000fec00000,0x00000000fecb33a8,0x00000000ff600000)
 Metaspace       used 3362K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 369K, capacity 388K, committed 512K, reserved 1048576K

/**
        System.gc();
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[2 * _1MB];
        allocation2 = new byte[2 * _1MB];
//        allocation3 = new byte[2 * _1MB];
//        allocation4 = new byte[4 * _1MB]; // Minor GC occurs once
*/
[GC (System.gc()) [PSYoungGen: 1994K->840K(9216K)] 1994K->848K(19456K), 0.0007088 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 840K->0K(9216K)] [ParOldGen: 8K->711K(10240K)] 848K->711K(19456K), [Metaspace: 3257K->3257K(1056768K)], 0.0030776 secs] [Times: user=0.06 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 9216K, used 4506K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 55% used [0x00000000ff600000,0x00000000ffa66808,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 10240K, used 711K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 6% used [0x00000000fec00000,0x00000000fecb1ce0,0x00000000ff600000)
 Metaspace       used 3337K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 363K, capacity 388K, committed 512K, reserved 1048576K

/**
        System.gc();
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[2 * _1MB];
        allocation2 = new byte[2 * _1MB];
        allocation3 = new byte[2 * _1MB];
//        allocation4 = new byte[4 * _1MB]; // Minor GC occurs once
*/
[GC (System.gc()) [PSYoungGen: 1994K->840K(9216K)] 1994K->848K(19456K), 0.0006964 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 840K->0K(9216K)] [ParOldGen: 8K->710K(10240K)] 848K->710K(19456K), [Metaspace: 3245K->3245K(1056768K)], 0.0028236 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 9216K, used 6554K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 80% used [0x00000000ff600000,0x00000000ffc66878,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 10240K, used 710K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 6% used [0x00000000fec00000,0x00000000fecb1ac8,0x00000000ff600000)
 Metaspace       used 3302K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 361K, capacity 388K, committed 512K, reserved 1048576K

/**
        System.gc();
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[2 * _1MB];
        allocation2 = new byte[2 * _1MB];
        allocation3 = new byte[2 * _1MB];
        allocation4 = new byte[4 * _1MB]; // Minor GC occurs once
*/
[GC (System.gc()) [PSYoungGen: 1994K->792K(9216K)] 1994K->800K(19456K), 0.0013828 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 792K->0K(9216K)] [ParOldGen: 8K->711K(10240K)] 800K->711K(19456K), [Metaspace: 3259K->3259K(1056768K)], 0.0031560 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 9216K, used 6570K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 80% used [0x00000000ff600000,0x00000000ffc6a828,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 10240K, used 4807K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 46% used [0x00000000fec00000,0x00000000ff0b1d60,0x00000000ff600000)
 Metaspace       used 3447K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 376K, capacity 388K, committed 512K, reserved 1048576K

Topics: jvm