Java learning pathSection 12

Garbage Collectors

Compare the major JVM collectors and their trade-offs for throughput, latency, and simplicity.

1

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.

Collector choice branches to Serial, Parallel, G1, ZGC, and Shenandoah, with their workload trade-offs; separate notes cover legacy CMS, Epsilon, and generational ZGC.
Collector choice is a workload trade-off
Example
java -version
java -Xlog:gc -jar application.jar
java -Xlog:gc* -jar application.jar
2

Serial 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.

Example
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);
        }
    }
}
3

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.

Example
java -XX:+UseParallelGC -Xms1g -Xmx1g -Xlog:gc GcDemo
4

CMS 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.

Example
# Historical only; do not use on a modern JDK
java -XX:+UseConcMarkSweepGC LegacyApplication
5

G1 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.

A G1 heap split into Eden, Survivor, Old and Free regions with the steps G1 uses to collect.
G1 divides the heap into regions
Example
java -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Xmx2g -Xlog:gc* GcDemo
6

ZGC

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.

Example
java -XX:+UseZGC -Xmx4g -Xlog:gc* GcDemo
7

Shenandoah 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.

Example
java -XX:+UseShenandoahGC -Xmx4g -Xlog:gc* GcDemo
8

Epsilon 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.

Example
java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -Xmx64m GcDemo
9

Generational 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.

Generational ZGC has young and old generations with concurrent marking and relocation, with a version timeline from JDK 21 to JDK 24.
Generational ZGC
Example
# JDK 24+
java -XX:+UseZGC -Xmx4g -Xlog:gc* GcDemo
10

Garbage Collector Comparison

Choose a collector based on measured latency, throughput, live data, and memory headroom, not its name.

Garbage Collector Comparison
CollectorMain emphasisConcurrent work?Key caution
SerialSimplicity / small heapNo for main collectionLong STW pauses
ParallelThroughputNo for main collectionTail latency
CMSLegacy low pauseYes, old-generation phasesRemoved in JDK 14
G1Balanced pause goalYes, marking phasesGoal not guaranteed
ZGCVery low latencyYes, most major workHeadroom / overhead
ShenandoahVery low latencyYes, much major workBuild availability
EpsilonNo GC overheadNo reclamationHeap exhaustion
Generational ZGCLow latency + agesYes, most major workSame as ZGC in JDK 24+
A comparison table of Serial, Parallel, G1, ZGC, Shenandoah, Epsilon and CMS by emphasis, concurrency and caution.
Collector comparison