Garbage Collectors in Java
The HotSpot JVM ships with several garbage collectors, each balancing three goals differently: throughput (how much of the CPU goes to your program instead of GC), latency (how long the application pauses), and memory footprint. You choose one with a command-line flag. In Java 25, the default on most machines is G1, a good all-rounder, while ZGC keeps pauses under a millisecond even on huge heaps. In this lesson, you will learn the five collectors and how to choose between them.
The Collectors
| Collector | Flag | How it works | Pauses | Best for |
|---|---|---|---|---|
| Serial | -XX:+UseSerialGC | One thread; stops the application for every collection | Longest | Small heaps, single-core machines, small containers |
| Parallel | -XX:+UseParallelGC | Many threads, but still stops the application | Medium | Batch jobs where total throughput matters most |
| G1 (default) | -XX:+UseG1GC | Splits the heap into regions; collects the most garbage-filled regions first; marks concurrently | Short, with a target (-XX:MaxGCPauseMillis, default 200 ms) | Most applications |
| ZGC | -XX:+UseZGC | Generational (Java 21+); marks and moves objects concurrently using colored pointers | Under 1 ms, independent of heap size | Large heaps and latency-sensitive services |
| Shenandoah | -XX:+UseShenandoahGC | Concurrent compaction | Very short | Low-latency services (available in OpenJDK builds) |
CMS, an older low-pause collector, was removed in Java 14.
Example: Which Collector Is Running?
Let's see an example that reports the active collector. Run it with different flags to see each collector's parts.
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
public class WhichGc {
public static void main(String[] args) {
for (GarbageCollectorMXBean gc :
ManagementFactory.getGarbageCollectorMXBeans()) {
System.out.println(gc.getName());
}
}
}G1 Young Generation
G1 Concurrent GC
G1 Old GenerationRun with each collector's flag, the same program prints:
-XX:+UseSerialGC Copy, MarkSweepCompact
-XX:+UseParallelGC PS MarkSweep, PS Scavenge
-XX:+UseG1GC G1 Young Generation, G1 Concurrent GC, G1 Old Generation
-XX:+UseZGC ZGC Minor Cycles, ZGC Minor Pauses, ZGC Major Cycles, ZGC Major Pauses
-XX:+UseShenandoahGC Shenandoah Pauses, Shenandoah CyclesExplanation: With no flag, the JVM chose G1. The other lines come from running the program with each flag (the names are joined on one line here to save space). Serial and Parallel name their young collector ("Copy", "PS Scavenge") and old collector separately. ZGC and Shenandoah report concurrent cycles separately from their short pauses.
How to Choose
- Start with the default (G1) and measure.
- If pauses are too long for your service, try ZGC.
- If the job is a batch where only total time matters, try Parallel.
- For tiny containers (one CPU, small heap), the JVM may pick Serial itself, and that's fine.
Common Mistakes
Tuning many GC flags at once. Change one thing, measure, repeat.
Choosing ZGC for small batch jobs. Its concurrent work costs some throughput; Parallel may finish sooner.
Looking for CMS. It was removed in Java 14.
Interview Corner
What garbage collectors are available in Java, and how do you choose?
HotSpot offers Serial (single-threaded, stop-the-world, for small heaps), Parallel (multi-threaded stop-the-world, for throughput), G1 (region-based, mostly concurrent marking with pause-time goals, the default), ZGC (concurrent, generational since Java 21, sub-millisecond pauses even for terabyte heaps) and Shenandoah (concurrent compaction, low pause). Start with G1; choose ZGC or Shenandoah when latency matters, Parallel when throughput matters and pauses don't, and Serial for very small footprints. Decide based on measured GC logs.
Practice
- Easy: Run
WhichGcwith-XX:+UseZGCand-XX:+UseSerialGCand compare the names. - Harder: Run a program that creates lots of garbage with
-Xlog:gcunder G1, Parallel and ZGC, and compare the reported pause times.
Key Takeaways
Was this tutorial helpful?