Garbage Collectors
Compare the major JVM collectors and their trade-offs for throughput, latency, and simplicity.
Garbage Collectors: Overview
A garbage collector identifies heap objects that are no longer reachable and reclaims their storage. Different collectors optimize different goals: throughput, pause latency, CPU use, and memory footprint. Collector choice never changes the rule that Java code must explicitly close external resources.
Concurrent means certain GC work overlaps with application execution; parallel means multiple GC threads work at the same time. A collector may be both. Flags and availability vary by JVM build.
java -version
java -Xlog:gc -jar application.jar
java -Xlog:gc* -jar application.jarSerial GC
Serial GC performs its core collection work with a single GC thread and pauses application threads during collection. Traditional HotSpot Serial GC is generational: young objects are evacuated and old-generation collections can compact retained objects.
The lack of multiple GC workers lowers coordination overhead but cannot exploit multiple cores. It suits small heaps, limited CPU environments, and simple batch workloads where longer pauses are acceptable.
The example allocates temporary arrays that become candidates for reclamation. The compiler can optimize allocations, so GC frequency is not guaranteed and the code does not demonstrate a particular pause duration.
java -XX:+UseSerialGC -Xms128m -Xmx128m -Xlog:gc GcDemo
public class GcDemo {
public static void main(String[] args) {
for (int i = 0; i < 10_000; i++) {
byte[] temporary = new byte[1024];
if (i % 1000 == 0) System.out.println(i);
}
}
}Parallel GC
Parallel GC, often called the throughput collector, uses multiple GC worker threads to shorten collection work while application execution is paused. Its design emphasis is throughput rather than minimal individual pauses.
More workers may reduce collection time but consume CPU. It suits CPU-rich batch jobs that prioritize total work over low tail latency. Compare Serial and Parallel using GC logs on your own machine; one run is not a reliable benchmark. Both can produce stop-the-world pauses.
java -XX:+UseParallelGC -Xms1g -Xmx1g -Xlog:gc GcDemoCMS GC (Legacy)
Concurrent Mark Sweep (CMS) was an older low-pause, generational HotSpot collector. It reclaimed old-generation memory mainly through concurrent marking and sweeping, reducing long stop-the-world work.
Simplified phases: initial mark (STW), concurrent mark, remark (STW), concurrent sweep. It did not routinely compact the old generation, so fragmentation was a concern.
CMS was deprecated in JDK 9 (JEP 291) and removed in JDK 14 (JEP 363). It was replaced because of fragmentation, implementation complexity, and better low-pause alternatives such as G1 and ZGC.
# Historical only; do not use on a modern JDK
java -XX:+UseConcMarkSweepGC LegacyApplicationG1 GC
G1 splits the heap into many similarly sized regions and selects regions for collection according to expected reclamation benefit and pause-time predictions. It is the default HotSpot collector on most modern server configurations.
Regions may act as Eden, Survivor, or Old. Young collections evacuate live objects, concurrent marking discovers old-region liveness, and mixed collections reclaim selected old regions alongside young ones. Remembered sets track references between regions.
The 200 ms pause value is a target, not a guarantee. G1 suits general-purpose services where reasonable throughput and controlled pauses both matter.
java -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Xmx2g -Xlog:gc* GcDemoZGC
The Z Garbage Collector is a low-latency, concurrent, relocating HotSpot collector. It performs much marking, reference processing, and object relocation while application threads run. Brief pauses still occur.
ZGC uses specialized reference metadata and barriers to keep references valid when objects move. It aims for short pauses even with large heaps but needs CPU and memory headroom to keep up with allocation. Do not assume it always has higher throughput than other collectors.
Non-generational ZGC is historical. Generational ZGC was introduced experimentally in JDK 21, became the default ZGC mode in JDK 23, and the non-generational mode was removed in JDK 24.
java -XX:+UseZGC -Xmx4g -Xlog:gc* GcDemoShenandoah GC
Shenandoah is a low-pause, concurrent, compacting collector developed in OpenJDK. Most marking and evacuation can overlap application execution, avoiding pause durations proportional to the live heap.
It divides the heap into regions, evacuates objects, and keeps references correct with read/load barriers and related mechanisms. Phases vary by mode and JDK.
Not every distribution or platform includes it, and recent OpenJDK builds also offer generational Shenandoah, so check your JDK. It suits strict latency needs, but concurrent work may add throughput or resource overhead.
java -XX:+UseShenandoahGC -Xmx4g -Xlog:gc* GcDemoEpsilon GC
Epsilon is a no-op collector: it allocates objects but does not reclaim heap memory. Once the configured heap is exhausted the application fails with OutOfMemoryError.
Use it for controlled experiments, performance measurements that isolate GC overhead, very short-lived processes, or explicitly memory-bounded workloads. Do not select it for a normal long-running service.
java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -Xmx64m GcDemoGenerational ZGC
Generational ZGC combines ZGC's concurrent, low-latency approach with young and old generations, building on the observation that many objects become unreachable soon after allocation.
Young collections focus on new objects and old collections manage long-lived ones. Remembered-set tracking and barriers handle cross-generation references while live objects can be relocated concurrently.
In JDK 21-22 generational mode needed -XX:+ZGenerational. It became the default in JDK 23, and non-generational mode was removed in JDK 24, so on JDK 24+ -XX:+UseZGC selects generational ZGC. Tutorials that recommend disabling it may be outdated.
# JDK 24+
java -XX:+UseZGC -Xmx4g -Xlog:gc* GcDemoGarbage Collector Comparison
Choose a collector based on measured latency, throughput, live data, and memory headroom, not its name.
| Collector | Main emphasis | Concurrent work? | Key caution |
|---|---|---|---|
| Serial | Simplicity / small heap | No for main collection | Long STW pauses |
| Parallel | Throughput | No for main collection | Tail latency |
| CMS | Legacy low pause | Yes, old-generation phases | Removed in JDK 14 |
| G1 | Balanced pause goal | Yes, marking phases | Goal not guaranteed |
| ZGC | Very low latency | Yes, most major work | Headroom / overhead |
| Shenandoah | Very low latency | Yes, much major work | Build availability |
| Epsilon | No GC overhead | No reclamation | Heap exhaustion |
| Generational ZGC | Low latency + ages | Yes, most major work | Same as ZGC in JDK 24+ |