Garbage collection
Generational GC, the main collectors, and how to reason about pauses.
What counts as garbage
An object is collectable when it can’t be reached from a GC root: local variables on thread stacks, static fields, active threads and JNI references. Reachability, not reference counting, so cycles are collected fine.
Generational heap
Most objects die young, so the heap is split:
- Young generation: Eden (new objects) + two Survivor spaces. A minor GC copies live objects from Eden to a survivor; objects that survive enough cycles are promoted.
- Old generation: long-lived objects, collected less often (major / full GC).
Copying the few survivors is cheap, which is why young collections are fast.
Phases
Mark live objects → sweep the dead → compact to remove fragmentation. A stop-the-world pause halts application threads; modern collectors do most of the marking concurrently.
Collectors
| Collector | Idea | Use when |
|---|---|---|
| Serial | single thread | tiny heaps, one CPU |
| Parallel | many threads, stop-the-world | throughput matters more than pauses (batch) |
| G1 (default since Java 9) | heap in regions, collects the most garbage first, pause-time target | most services |
| ZGC / Shenandoah | almost everything concurrent, pauses < a few ms | large heaps, latency-sensitive |
Reasoning about GC in production
- Frequent full GCs or growing old gen after each GC → likely a memory leak (e.g. an unbounded cache, listeners never removed).
- Tune by sizing the heap (
-Xms/-Xmx) and setting a pause goal (-XX:MaxGCPauseMillis) before touching anything else. - Read GC logs (
-Xlog:gc*) and watch allocation rate; allocating less is the best tuning.
Reference types
Strong (normal) · Soft (cleared under memory pressure, for caches) · Weak (cleared at next GC, WeakHashMap) · Phantom (cleanup after finalisation).