Garbage Collection: Precision Internals and Performance Tuning
Garbage Collection (GC) is one of the most defining features of Java, separating it from the manual memory management of C/C++. JVM automatically identifies and reclaims "dead" objects, freeing developers from the burden of calling free().
However, "automatic" does NOT mean "free." GC introduces Stop-The-World (STW) pauses, and different collectors make critical trade-offs between Throughput, Latency, and Footprint. Understanding the underlying mechanics of GC is a mandatory skill for high-performance Java engineering.
1. The Generational Memory Model
Most modern JVMs (HotSpot) partition the heap into Young and Old generations. This design is based on the Weak Generational Hypothesis:
- Infant Mortality: Most objects die young (e.g., local variables).
- Survivorship: Objects that survive multiple GC cycles tend to live for a long time (e.g., caches, singletons).
1.1 Heap Layout
graph LR
subgraph Young["Young Generation (1/3)"]
Eden[Eden - 8/10]
S0[S0 - 1/10]
S1[S1 - 1/10]
end
subgraph Old["Old Generation (2/3)"]
OldGen[Tenured/Old Gen]
end
1.2 Why Two Survivors?
The Young Generation uses the Copying Algorithm. By maintaining two Survivor spaces ($S0$ and $S1$), the GC can always move live objects from Eden and the "active" Survivor space into a completely "clean" target space. This eliminates memory fragmentation without the cost of a complex compaction phase.
2. Garbage Identification: Who is "Dead"?
2.1 Reachability Analysis (The Truth)
JVM uses Reachability Analysis to find garbage. Starting from a set of GC Roots, it traverses the reference graph. Any object reachable from a root is "live"; all others are "garbage."
Typical GC Roots:
- Local Variables: Active references on the VM Stack.
- Static Fields: References held by classes in the Method Area (Metaspace).
- JNI References: Objects used by C/C++ native code (Native Stack).
- Synchronized Monitors: Objects used as locks in active blocks.
- Active Threads: All objects reachable from running thread stacks.
3. The Core GC Algorithms
3.1 Mark-Sweep
- Process: Mark live objects; sweep away the rest.
- Flaw: Fragmentation. Leaves "holes" in memory, making it impossible to allocate large contiguous objects later.
3.2 Copying (Young Gen Specialist)
- Process: Split memory in half; copy live objects from one half to the other.
- Benefit: $O(Live Objects)$ complexity. In the Young Gen, where 95%+ of objects die, it only copies the tiny survivor set.
- Optimization: HotSpot uses 8:1:1 ratio (Eden:S0:S1) so only 10% of space is "wasted."
3.3 Mark-Compact (Old Gen Specialist)
- Process: Mark live objects and slide them toward one end of the heap.
- Benefit: Zero fragmentation. Essential for long-lived regions where free space is scarce.
4. The Evolution of Collectors: The War on STW
Generation 1: Serial (The Pioneer)
- Mechanism: Single-threaded STW.
- Best For: Tiny containers (Docker) or CLI tools with low memory/CPU overhead.
Generation 2: Parallel (Throughput King - JDK 8 Default)
- Mechanism: Multi-threaded STW.
- Best For: Batch processing, analytics, and offline tasks where total execution speed matters more than individual pause times.
Generation 3: CMS (Low Latency Pioneer)
The first to use Concurrent Marking. It performs the heavy lifting of identifying garbage while the application is still running.
- Initial Mark (STW): Finds direct roots (very fast).
- Concurrent Mark: Traverses the graph (slow, but asynchronous).
- Remark (STW): Fixes changes made by users during the concurrent phase.
- Concurrent Sweep: Deletes garbage.
- Flaw: No compaction = Fragmentation. Eventually triggers a brutal
Serial OldFallback.
Generation 4: G1 (Region-Based Master - JDK 9+ Default)
G1 breaks the heap into hundreds of Regions.
- Garbage-First: It prioritizes reclaiming regions with the most garbage to get the best "Bang for the buck" within a user-defined pause target (
-XX:MaxGCPauseMillis). - Mixed GC: Can reclaim Young and Old regions simultaneously.
- Algorithm: Micro-copying between regions ensures zero fragmentation.
Generation 5: ZGC (Sub-Millisecond Pauses)
ZGC aims for pauses < 1ms, regardless of heap size (up to 16TB).
- Colored Pointers: Labels pointers directly in their 64-bit addresses.
- Load Barriers & Self-Healing: If a user tries to access an object that the GC just moved, the "Load Barrier" intercepts the call, serves the new address, and updates the reference in-place. This allows the "Copying" phase to be fully concurrent.
5. Promotion and Tuning
5.1 Promotion Rules
- Age: Objects survive 15 (default) Minor GCs and get promoted to Old Gen.
- Size: Massive objects go straight to Old Gen to avoid copying.
- Survivor Overflow: If Survivor space is full, objects overflow to Old Gen.
5.2 Tuning Methodology
- Stabilize: Set
-Xmsand-Xmxto the same value to avoid "Thermal Oscillation" (heap resizing overhead). - Observe: Use
-Xlog:gc*to monitor frequencies and STW durations. - G1 Target: Set
-XX:MaxGCPauseMillis=200to balance latency and throughput.
Summary Matrix
| Collector | Algorithm | Phase | Best Use Case |
|---|---|---|---|
| Serial | Copy/Compact | Single-threaded | Tiny containers |
| Parallel | Copy/Compact | Multi-threaded | Batch / High Throughput |
| CMS | Copy/Sweep | Concurrent | Low Latency (Legacy) |
| G1 | Micro-copying | Priority Regions | Modern Standard (JDK 9+) |
| ZGC | Color/Self-heal | Fully Concurrent | Zero Pause / Massive Heaps |