Short-lived java applications: How to tune G1 to kick in later?

Viewed 137

I've got short-living applications which usually (but not always) do not need any GC (fits in heap, epsilon GC proves this by not causing an OOM).

Interestingly, G1 still kicks in very early even though there's still plenty of heap free:

[0.868s][info   ][gc,start     ] GC(0) Pause Young (Normal) (G1 Evacuation Pause)
[0.869s][info   ][gc,task      ] GC(0) Using 13 workers of 13 for evacuation
[0.872s][info   ][gc,phases    ] GC(0)   Pre Evacuate Collection Set: 0.0ms
[0.873s][info   ][gc,phases    ] GC(0)   Evacuate Collection Set: 2.8ms
[0.873s][info   ][gc,phases    ] GC(0)   Post Evacuate Collection Set: 0.4ms
[0.873s][info   ][gc,phases    ] GC(0)   Other: 1.0ms
[0.873s][info   ][gc,heap      ] GC(0) Eden regions: 51->0(45)
[0.873s][info   ][gc,heap      ] GC(0) Survivor regions: 0->7(7)
[0.873s][info   ][gc,heap      ] GC(0) Old regions: 0->2
[0.873s][info   ][gc,heap      ] GC(0) Humongous regions: 4->2
[0.873s][info   ][gc,metaspace ] GC(0) Metaspace: 15608K->15608K(1062912K)
[0.874s][info   ][gc           ] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 55M->10M(1024M) 5.582ms
[0.874s][info   ][gc,cpu       ] GC(0) User=0.00s Sys=0.00s Real=0.01s
[...]

It makes me wonder why GC runs here at all as heap is only 55MB.
In total I have usually 10-15 GC runs which aggregate to a consumed user cpu time of ~1 second which I'd like to avoid.

JVM: openjdk version "11.0.16" 2022-07-19
JVM ARGS: -Xms1g -Xmx2g -XX:+PrintGCDetails -Xlog:gc+cpu=info -Xlog:gc+heap+exit 

Question:
How can I tune G1 (jdk 11) to kick in as late as possible (e.g. when heap/eden is 90% full) to ideally avoid any GC pauses/runs in most of my cases?
Increasing -XX:InitiatingHeapOccupancyPercent (e.g. to 90%) did not help in my case.


EDIT:

Try it out by yourself by executing this java class on your jvm:

public class GCTest {
    public static void main(String[] args) {

        java.util.Map<String,byte[]> map = new java.util.HashMap<>();
        
        for(int i=0;i<1_000_000;i++)
            map.put(i+"", new byte[i % 256]);   
        
        System.out.println(map.size());
    }
}

This application consumes about 260MB heap and runs less than 500ms.
When started with the following jvm arguments:
-Xms1g -Xmx2g -XX:+PrintGCDetails -Xlog:gc+cpu=info -Xlog:gc+heap+exit
you will get ~5-6 GC runs (tested with java 11+16 hotspot vm) .
GC Epsilon tests clearly shows that it can run without any GCing.

Challenge:
Can you find jvm arguments which will force G1 to not do any GCing here?

1 Answers

You can't escape from GC but we can stay away for a while. The most important question is when GC trigger?

When Eden size is full, GC will trigger for minor GC. That means if we set bigger Young Generation size, GC will not trigger because Young Generation is not full yet. So another question is coming. How can we set young collection size ?

JVM has -Xmn argument. This argument sets the initial and maximum size of the heap for the young generation. Initial is important keyword in this explanation. In Oracle Doc:

-Xmn size Sets the initial and maximum size (in bytes) of the heap for the young generation (nursery) in the generational collectors. Append the letter k or K to indicate kilobytes, m or M to indicate megabytes, or g or G to indicate gigabytes. The young generation region of the heap is used for new objects. GC is performed in this region more often than in other regions. Instead of the -Xmn option to set both the initial and maximum size of the heap for the young generation, you can use -XX:NewSize to set the initial size and -XX:MaxNewSize to set the maximum size.

So when apply -Xmn argument like -Xmn300m -Xmx512m -XX:+PrintGCDetails -Xlog:gc+cpu=info -Xlog:gc+heap+exit we will not see GC pause. I tried for your example. Result:

[0.002s][warning][gc] -XX:+PrintGCDetails is deprecated. Will use -Xlog:gc* instead.
[0.007s][info   ][gc,heap] Heap region size: 1M
[0.015s][info   ][gc     ] Using G1
[0.015s][info   ][gc,heap,coops] Heap address: 0x00000000e0000000, size: 512 MB, Compressed Oops mode: 32-bit
1000000
[0.321s][info   ][gc,heap,exit ] Heap
[0.321s][info   ][gc,heap,exit ]  garbage-first heap   total 522240K, used 245760K [0x00000000e0000000, 0x0000000100000000)
[0.321s][info   ][gc,heap,exit ]   region size 1024K, 221 young (226304K), 0 survivors (0K)
[0.321s][info   ][gc,heap,exit ]  Metaspace       used 6722K, capacity 6815K, committed 7040K, reserved 1056768K
[0.321s][info   ][gc,heap,exit ]   class space    used 596K, capacity 613K, committed 640K, reserved 1048576K

**Don't forget, if application trigger the GC(create object more than young collection size) with these arguments, you can see latency problem because GC can take a long time to complete

Related