javamemory-managementjvmgarbage-collectiong1gc

Why isn't RSS reduced when System.gc() for parallel, but for G1 is?


I'm using Java 17. I have a container with 2GB and run the following Java program:

import java.util.*;

public class Main{
        public static Map<byte[], byte[]> m = new HashMap<>();

        public static void main(String args[]) throws Exception {
                int i = 0;
                while(true){
                        i++;
                        byte b[] = new byte[1024 * 1024];
                        m.put(b, b);
                        if(i % 700 == 0){
                                System.out.println("Reached limit: " + i);
                                m = new HashMap<>();
                                Thread.sleep(500000);
                                System.gc();
                                Thread.sleep(500000);
                        }
                }
        }
}

Running it with the options

java -XX:+UseParallelGC \
     -XX:InitialRAMPercentage=40 \
     -XX:MaxRAMPercentage=40 \
     -XX:-ShrinkHeapInSteps \
     -XX:MinHeapFreeRatio=10 \
     -XX:MaxHeapFreeRatio=10 Main

I keep receiving that the RSS reported by pmap of the heap is 800MB, it's not getting lower.

     Address Perm   Offset Device   Inode    Size    Rss    Pss Referenced Anonymous 
    ccc00000 rw-p 00000000  00:00       0  839680 838812 838812     838812    838812       

But it's pretty clear that System.gc() is applied and not ignored:

[5.871s][info ][gc             ] GC(4) Pause Full (System.gc()) 702M->1M(792M) 2.820ms

So neither -XX:-ShrinkHeapInSteps -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=10 -XX:+UseParallelGC nor System.gc() makes RSS being reduced.

I need the RSS to be reduced since most of the time Java heap must be small and it's simply inefficient to consume so much RSS for nothing.

Notably, System.gc() can reduce the amount of RSS of G1 GC and works as expected.

Is there a way to do that for parallel and why does System.gc() required for G1 and simply full GC is not enough?


Solution

  • Parallel GC never uncommits memory reserved for Java Heap, while G1 may uncommit unused memory (i.e. return it back to the OS). Before JDK 12, G1 could return memory only after a full GC or a concurrent cycle. Since JDK 12, it can uncommit more often - see JEP 346 for details.

    See also: Memory footprint of a Java process