When pause times matter more than raw throughput, the JVM offers two concurrent collectors that keep pauses below a millisecond, plus Epsilon, which skips collection entirely. This part also covers what Java 25 changed.
ZGC - The Time Lord
- Pauses
- Under 10 ms at any heap size
- Throughput
- At most 15% overhead
- Heap
- Up to 16 TB
- GC threads
- Many, almost all concurrent
Ultra-Low Latency at Scale
Z Garbage Collector (ZGC) is Oracle’s answer to the latency problem. Design goals:
- Pause times under 10ms regardless of heap size
- Scalability to 16TB heaps
- No more than 15% throughput overhead
ZGC achieves this through a combination of techniques:
- Concurrent operations: Marking, relocation, reference processing - all happen while application threads run
- Colored pointers: Pointer metadata tracks object state (marked, relocated, etc.) without touching the object itself
- Load barriers: Every object read checks if relocation is needed, enabling concurrent compaction
- Generational by default (Java 23+): Separates young and old collections for better efficiency - now the standard mode
The ZGC Architecture
- Application thread → Load barrierReads a reference
- Load barrierChecks the colour
- Load barrier → GC relocationObject moved?
- GC relocation → Load barrierNew address
- Load barrierHeals the pointer
- Load barrier → Application threadCorrect reference
Enabling ZGC
# Java 15-21 (non-generational)
java -XX:+UseZGC -Xms16g -Xmx16g MyApp
# Java 21-22 (generational ZGC - explicit flag required)
java -XX:+UseZGC -XX:+ZGenerational -Xms16g -Xmx16g MyApp
# Java 23+ (generational by default)
java -XX:+UseZGC -Xms16g -Xmx16g MyApp
# Java 24+ (non-generational mode removed - JEP 490)
java -XX:+UseZGC -Xms16g -Xmx16g MyAppNote: ZGC is generational by default since Java 23. The
-XX:+ZGenerationalflag was deprecated in Java 23 and non-generational mode was completely removed in Java 24 (JEP 490). In Java 25, generational ZGC is mature and production-hardened.Note: On Java 25, ZGC works with Compact Object Headers (JEP 519,
-XX:+UseCompactObjectHeaders), so the two can be combined.
When to Choose ZGC
- Sub-10ms p99 latency: consistently delivers
- Large heaps (16GB+): pause time independent of heap size
- Cloud/container environments: efficient memory return to OS
- Latency-sensitive services: no tuning typically needed
The Trade-Off
ZGC uses more CPU and memory than G1 or Parallel. It reserves address space aggressively and has higher memory overhead. But for latency-critical applications, this is a fair exchange.
Container Tip: ZGC works well in containers but set
-XX:MaxRAMPercentage=75.0to leave headroom for GC overhead. Don’t allocate 100% of container memory to the heap.
Shenandoah - The Memory Whisperer
- Pauses
- Under 10 ms at any heap size
- Throughput
- Slightly higher than ZGC
- Heap
- Up to 16 TB
- GC threads
- Many, evacuation concurrent
Concurrent Compaction Pioneer
Developed by Red Hat and merged into OpenJDK, Shenandoah shares ZGC’s goals but uses different techniques:
- Concurrent evacuation: Objects are moved while application threads run
- Forwarding pointers: The old copy of an evacuated object points to the new one. Shenandoah started with a separate Brooks pointer in every object; since JDK 13 the forwarding pointer lives in the header of the old copy instead
- Load reference barriers: Every reference loaded from the heap resolves to the current copy of the object. They replaced the earlier read and write barriers in JDK 13
- Pause times independent of heap size - a 200GB heap collects as fast as a 2GB heap
Shenandoah’s Phases
- Initial mark pause
- Marking
- Final mark pause
- Evacuation
- Update references
- Final update refs pause
Enabling Shenandoah
# Available in OpenJDK 12+, Red Hat/OpenLogic builds
java -XX:+UseShenandoahGC -Xms8g -Xmx8g MyApp
# Heuristics: when Shenandoah starts a cycle
java -XX:ShenandoahGCHeuristics=adaptive ... # Default: from the timing of previous cycles
java -XX:ShenandoahGCHeuristics=static ... # From heap occupancy
java -XX:ShenandoahGCHeuristics=compact ... # Back to back: smallest heap, lower throughputZGC vs Shenandoah
Both are excellent choices. In 2026, both ZGC and Shenandoah have generational modes. ZGC’s generational implementation has been default since Java 23, while Shenandoah added generational support in Java 25 (JEP 521). For ultra-low latency workloads, both collectors now offer mature, production-ready generational collection.
The Epsilon Collector - The Sound of Silence
- Pauses
- None, it never collects
- Throughput
- No GC overhead
- Heap
- Until it fills, then the JVM exits
- GC threads
- None
No-Op for Performance Testing
Epsilon is the anti-collector - it allocates memory but never collects. When the heap fills up, the JVM exits.
Use it for:
- Performance testing (eliminate GC as a variable)
- Short-lived applications (one-shot tools)
- Determining if you need a collector at all (if Epsilon works, you have no GC pressure)
java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC MyAppWarning: Epsilon is not for production. Your application will crash when memory fills. This is by design.
Java 25 Innovations - The New Frontier
Generational Shenandoah (JEP 521)
While ZGC went generational by default in Java 23, Java 25 brought the same treatment to Shenandoah (JEP 521). This is significant because:
- Shenandoah has lower memory overhead than ZGC
- Both collectors now offer generational modes
- Applications with typical object lifetime patterns see reduced pause times
Enable it (Java 25+):
java -XX:+UseShenandoahGC -XX:ShenandoahGCMode=generational -Xms8g -Xmx8g MyAppThe Java 25 LTS Recommendation
For production deployments in 2026, Java 25 LTS (released September 2025) should be your target:
# Recommended Java 25 configuration for low-latency services
java \
-XX:+UseZGC \
-XX:+AlwaysPreTouch \
-Xms4g \
-Xmx4g \
-jar application.jarDeep dive: For detailed coverage of Java 25’s performance improvements including Project Leyden’s AOT profiling and Scoped Values, see JVM: The Silent Revolution.