The JVM specification intentionally leaves garbage-collection details to each vendor and version, so HotSpot alone has evolved a dozen distinct collectors. This article reviews the first six "classic" algorithms—Serial, ParNew, Parallel Scavenge, Serial Old, Parallel Old and CMS—and explains why G1 was eventually introduced.
Classifying Collectors
| Axis | Choices |
|---|---|
| Threading | Serial (single GC thread) vs. Parallel (multiple GC threads) |
| Interleaving | Stop-the-world (STW) vs. Concurrent (GC and app threads interleave) |
| Compaction | Compacting (no fragmentation) vs. Non-compacting (may fragment) |
| Heap region | Young-only, Old-only, or Whole-heap |
Performance Metrics
- Throughput = user-code-time / (user-code-time + GC-time)
- Pause time = duration of any single STW event
- Footprint = maximum Java heap size
- Promptness = time between object death and reclamation
These three goals form an "impossible triangle": improving any two usually hurts the third.
Historical Timeline
1999 JDK 1.3.1 Serial GC (first collector)
2002 JDK 1.4.2 Parallel GC + CMS
2006 JDK 6 Parallel GC becomes default
2012 JDK 7u4 G1 becomes usable
2017 JDK 9 G1 becomes default
2018 JDK 11 ZGC (experimental)
2019 JDK 12 Shenandoah (experimental)
2020 JDK 14 CMS removed
Collector Matrix
| Collector | Target | Algorithm | Threads | Compaction |
|---|---|---|---|---|
| Serial | Young | Copy | 1 | Yes |
| Serial Old | Old | Mark-Compact | 1 | Yes |
| ParNew | Young | Copy | Parallel | Yes |
| Parallel Scavenge | Young | Copy | Parallel | Yes |
| Parallel Old | Old | Mark-Compact | Parallel | Yes |
| CMS | Old | Mark-Sweep | Concurrent phases | No |
| G1 | Whole heap | Regional Copy/Mark-Compact | Parallel + Concurrent | Yes |
Serial Family (Serial + Serial Old)
Serial is the simplest collector: one thread, STW, copying for the young generation and mark-compact for the old. Its still the default on 32-bit Client VMs and tiny heaps where the pause is only a few tens of milliseconds. Enable with:
-XX:+UseSerialGC
ParNew
ParNew is literally a multi-threaded copy of Serial for the young generation. It shines on multi-core machines and is the only young collector that pairs with CMS. Thread count defaults to the number of hardware threads but can be fixed:
-XX:+UseParNewGC -XX:ParallelGCThreads=8
Parallel Throughput Family (Parallel Scavenge + Parallel Old)
These collectors aim for maximum throughput, not minimum pause. Parallel Scavenge uses an adaptive sizing policy that tunes Eden/Survivor sizes, tenuring threshold and heap size automatically. Key flags:
-XX:+UseParallelGC # young
-XX:+UseParallelOldGC # old (auto-enabled when above is set)
-XX:MaxGCPauseMillis=200 # hint to collector
-XX:GCTimeRatio=99 # 1 % of time spent in GC
-XX:+UseAdaptiveSizePolicy # let JVM self-tune
CMS (Concurrent Mark-Sweep)
CMS was the first low-latency collector. It performs most of its work concurrently with application threads, reducing pause times to initial-mark and remark phases only. The trade-offs are:
- Uses mark-sweep → fragmentation
- Needs spare heap space to avoid "concurrent-mode failure"
- Cannot compact concurrently
- CPU sensitive: steals cycles from the application
- Produces "floating garbage" (objects that die after the concurrent mark)
Typical configuration:
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction=5
-XX:ParallelCMSThreads=4
CMS was deprecated in JDK 9 and removed in JDK 14.
Quick Selection Guide
- Minimal overhead, tiny heap → Serial
- Max throughput, batch jobs → Parallel
- Low pause, interactive app → CMS (legacy) or G1/ZGC/Shenandoah (modern)
There is no universal collector; choose the one whose trade-offs best match your workload.