We are seeing significant 'Stop-the-World' pauses in our high-frequency trading platform. We recently upgraded to Java 21 and are experimenting with ZGC and Shenandoah. Which GC is better for minimizing latency without sacrificing too much throughput? Are there specific JVM flags that are now obsolete with these modern collectors?
3 answers
For low latency, Generational ZGC (introduced in Java 21) is currently the gold standard. It aims for sub-millisecond pause times by performing almost all work concurrently with the application threads. Unlike the older G1GC, ZGC scales well with very large heaps (from 8MB to 16TB). You should start with the flag -XX:+UseZGC and -XX:+ZGenerational. You'll find that many old tuning flags like -Xmn or -XX:SurvivorRatio are largely unnecessary now, as the modern collectors are much more self-tuning.
Does ZGC still have a throughput penalty? I heard it uses more CPU cycles to achieve those low pause times compared to Parallel GC.
Just make sure you aren't over-allocating your heap. Modern GCs work best when they have enough breathing room to move objects around.
Good advice, Lisa. Keeping the heap size balanced is key. I've found that setting Xms and Xmx to the same value also helps stabilize performance in Java 21.
Richard, you're correct that there is a slight overhead because the GC threads are working alongside your app. However, with Generational ZGC, this 'tax' has been significantly reduced. In most real-world scenarios, the benefit of avoiding a 200ms pause outweighs a 3-5% drop in raw throughput. If your app is extremely CPU-bound and can tolerate pauses, Parallel GC is still the king of throughput.