Skip to content
TobussSystems in Practice

Java Garbage Collection Cheat Sheet

2 min read

A practical reference covering GC collector trade-offs, G1’s region-based layout, low-pause collectors (ZGC/Shenandoah), and how to diagnose GC behavior in production.


Collector Trade-offs

Trade-off triangle: throughput vs pause time vs memory footprint. No GC wins on all three.

Collector      Throughput   Pause Time   Use Case
─────────────────────────────────────────────────────
Serial         Low          High         Single-core, small heaps
Parallel       High         Medium       Batch processing, throughput focus
CMS            Medium       Low(ish)     Deprecated in Java 14
G1 (default)   High         Predictable  General purpose (Java 9+)
ZGC            High         Sub-ms       Latency-critical (Java 15+)
Shenandoah     High         Sub-ms       Same as ZGC, different algorithm

G1 — Region-Based Heap Layout

Heap divided into ~2048 equal-sized regions (1–32 MB each). Regions can be Eden, Survivor, Old, or Humongous (large objects). G1 prioritizes regions with most garbage first — hence “Garbage First”. Mixed GC collects both young and old regions together.


ZGC & Shenandoah — Concurrent Low-Pause Collectors

Both achieve sub-millisecond pauses by doing most GC work concurrently with the application. They use load barriers to handle object references being moved while the app runs. Higher CPU overhead (~5–15%) — the cost of concurrency. Ideal for payment APIs and low-latency services.

# Enable ZGC (Java 15+ for production)
-XX:+UseZGC
-XX:SoftMaxHeapSize=4g         # soft limit — ZGC tries to stay under this

# ZGC phases (all concurrent, no stop-the-world except tiny pauses):
# 1. Mark start (pause ~1ms)
# 2. Concurrent mark
# 3. Mark end (pause ~1ms)
# 4. Concurrent process references
# 5. Concurrent relocate
# 6. Concurrent remap

# Shenandoah
-XX:+UseShenandoahGC
-XX:ShenandoahGCMode=iu       # incremental-update mode (default)

GC Logging & Diagnostics

Enable GC logging with -Xlog:gc* (Java 9+). Key things to look for: frequency of collections, pause duration, heap size before/after, allocation rate. A Full GC is always a red flag — it stops all threads.

# Enable GC logging
-Xlog:gc*:file=gc.log:time,uptime,level,tags

# Sample G1 log output:
[2.456s][info][gc] GC(3) Pause Young (Normal) (G1 Evacuation Pause)
[2.456s][info][gc] GC(3)   Heap: 512M -> 128M (1024M)
[2.456s][info][gc] GC(3)   Pause: 12.3ms

# Red flags:
# - "Full GC" → heap pressure, possible leak, or wrong GC tuning
# - Pause > MaxGCPauseMillis target consistently
# - Heap size after GC growing each cycle → leak
# - Allocation rate spiking → short-lived object pressure

# Useful tools:
# - GCViewer (open source)
# - GCEasy (web-based)
# - JDK's built-in: jstat -gcutil <pid> 1000