How JVM Garbage Collection Works: Types, Memory Impact, and Tuning
Diagnosing a GC Problem: A Step-by-Step Production Walkthrough
JVM garbage collection automatically reclaims unused heap memory, but every algorithm makes a different trade-off between throughput, latency, and memory footprint.
🎯 System Scenario & Requirements
System Goal
JVM garbage collection automatically reclaims unused heap memory, but every algorithm makes a different trade-off between throughput, latency, and memory footprint.
Actors & Components
- Application or API tier
- Core service under design
- Operator or SRE observer
Functional Requirements
- TLDR: JVM garbage collection automatically reclaims unused heap memory, but every algorithm makes a different trade-off between throughput, latency, and memory footprint.
- The default G1GC targets 200ms pause goals and works well for most services.
- For sub-millisecond pauses on large heaps, use ZGC (Java 15+).
Constraints & Assumptions
- Every GC algorithm sits on a triangle with three vertices: throughput, latency, and footprint.
- You can fully optimise two of the three, but not all three simultaneously.
- OutOfMemoryError: Java heap space The most common GC failure.
- The heap is full and the JVM cannot allocate a new object.
⚡ Execution Stages & Decision Trace
Stage 1 — When the JVM Stops the World: A Production Incident
Your payment microservice is paging the on-call engineer.
P99 latency has spiked from 180 ms to 34,000 ms.
Deep Dive
Scenario context
JVM garbage collection automatically reclaims unused heap memory, but every algorithm makes a different trade-off between throughput, latency, and memory footprint. The default G1GC targets 200ms pause goals and works well for most services. For sub-millisecond pauses on large heaps, use ZGC (Java 15+). For pure batch throughput, Parallel GC wins. Understanding heap regions — Eden, Survivor, Old Gen, Metaspace — tel…
This walkthrough focuses on Diagnosing a GC Problem: A Step-by-Step Production Walkthrough.
Before making a design choice, clarify:
- This section demonstrates why the GC concepts above matter in a real scenario — not just as theory but as a diagnostic workflow you can apply the next time GC causes a production incident.
- The scenario: A Spring Boot API service running on Java 17 with G1GC begins showing elevated P99 latency.
- Response times that were consistently under 150 ms are now occasionally spiking to 600–900 ms.
Concepts in play: jvm-internals-and-garbage-collection, tree-data-structures, contract-testing, stack-and-queue-data-structures
Stage 1 — When the JVM Stops the World: A Production Incident
- Your payment microservice is paging the on-call engineer.
- P99 latency has spiked from 180 ms to 34,000 ms.
- The load balancer is timing out requests.
- Customers cannot complete checkout.
Observable outcome: Customers cannot complete checkout.
Stage 2 — Inside the JVM Heap: Memory Regions and Their Roles
- Before examining any GC algorithm, you need a clear model of the heap's internal structure.
- If you want the stack-vs-heap baseline first, see Java Memory Model: Stack vs.
- The JVM divides memory into several distinct regions, each with a different object lifecycle role.
- The diagram below shows the full JVM memory layout.
graph TD
JVM[JVM Process Memory] --> Heap[Heap - GC Managed]
JVM --> NonHeap[Non-Heap Memory]
Heap --> YoungGen[Young Generation]
Heap --> OldGen[Old Generation - Tenured Space]
YoungGen --> Eden[Eden Space - new allocations]
YoungGen --> S0[Survivor Space S0]
YoungGen --> S1[Survivor Space S1]
NonHeap --> Metaspace[Metaspace - class metadata]
NonHeap --> CodeCache[Code Cache - JIT compiled code]
NonHeap --> Stacks[Thread Stacks - per-thread frames]
Observable outcome: The diagram below shows the full JVM memory layout.
Stage 3 — Young Generation Collections: How Minor GC Keeps Eden Flowing
- A Minor GC fires whenever Eden Space is full.
- It operates exclusively on the Young Generation, which is why it is fast — typically 5 to 50 ms even on large heaps.
- The collection algorithm is mark-copy : live objects are identified (marked) by tracing from GC Roots, then copied to the active Survivor space.
- The old Eden and the previously-active Survivor space are then cleared in a single sweep.
flowchart TD
Alloc["Object allocated in Eden Space via pointer-bump"] --> EdenFull{"Eden Space full?"}
EdenFull -->|No| MoreAlloc["Continue allocating more objects in Eden"]
MoreAlloc --> EdenFull
EdenFull -->|Yes| STWPause["Stop-the-World pause begins - all threads halt"]
STWPause --> MarkRoots["Trace GC Roots to identify all reachable live objects"]
MarkRoots --> CopyLive["Copy live objects into the active Survivor space"]
CopyLive --> IncrAge["Increment age counter on each surviving object"]
IncrAge --> ClearEden["Clear Eden and the now-exhausted Survivor space"]
ClearEden --> STWEnd["Stop-the-World pause ends - application threads resume"]
IncrAge --> AgeCheck{"Object age reached tenuring threshold?"}
AgeCheck -->|No| StaySurvivor["Object remains in Survivor space for next GC cycle"]
AgeCheck -->|Yes| PromoteOld["Object promoted to Old Generation"]
PromoteOld --> OldGenGrows["Old Generation fill level increases"]
Observable outcome: The old Eden and the previously-active Survivor space are then cleared in a single sweep.
Stage 4 — Old Generation, Metaspace, and GC Roots: The Memory Management Foundation
- The Old Generation accumulates promoted objects.
- Its collection algorithms vary by GC choice, but all share the same trigger: Old Gen fill percentage crosses a threshold (default 45% for G1, configurable for others).
- Unlike Minor GC, which uses mark-copy on a small region, Old Generation collection must handle a much larger space with a different algorithm: - Mark-Sweep-Compact : mark live objects → sweep dead ones → compact remaini…
- Used by Serial and Parallel GC.
Observable outcome: Used by Serial and Parallel GC.
Stage 5 — Metaspace: The Class Metadata Region That Lives Outside the Heap
- Metaspace replaced PermGen in Java 8.
- Before this change, class metadata was stored in a fixed-size heap region — a PermGen full error was a common deployment failure in heavy frameworks like JBoss or OSGi.
- Metaspace solves the fixed-size problem by allocating class metadata in native (OS-managed) memory.
- The downside: without an explicit cap, a class loader leak will grow Metaspace until the JVM crashes with OutOfMemoryError: Metaspace .
Observable outcome: The downside: without an explicit cap, a class loader leak will grow Metaspace until the JVM crashes with OutOfMemoryError: Metaspace .
Stage 6 — The Six GC Algorithms: Serial, Parallel, CMS, G1, ZGC, and Shenandoah
- The JVM ships with six distinct garbage collection implementations.
- Each makes a specific engineering trade-off.
- Here is how to understand each one.
- GC Algorithm Internals: Mark, Copy, Sweep, and Compact Every GC algorithm is built from four fundamental operations: mark (identify live objects by tracing from GC roots), copy (move live objects to a new region — used…
flowchart TD
DB["Database
(WAL/Log)"]
Capture["Capture Engine"]
Transform["Transform"]
Sink["Sink/Destination"]
DB -->|"Change Log"| Capture
Capture -->|"CDC Event"| Transform
Transform -->|"Normalized"| Sink
Observable outcome: GC Algorithm Internals: Mark, Copy, Sweep, and Compact Every GC algorithm is built from four fundamental operations: mark (identify live objects by tracing from GC roots), copy (move live objects to a new region — used…
End-to-end outcome
After working through the stages, you should be able to explain:
- Heap structure drives GC behavior.
- Eden → Survivor → Old Gen is the standard promotion path.
Alternatives considered
The source focuses on one primary design. A second architecture is not compared, so no alternative is invented here.
Failure and degraded modes
The GC Throughput vs. Latency vs. Footprint Trade-Off Triangle
- Detection: Every GC algorithm sits on a triangle with three vertices: throughput, latency, and footprint.
- User or operator impact: Every GC algorithm sits on a triangle with three vertices: throughput, latency, and footprint.
OutOfMemoryError: Java heap space
- Detection: The heap is full and the JVM cannot allocate a new object.
- User or operator impact: The heap is full and the JVM cannot allocate a new object.
OutOfMemoryError: GC overhead limit exceeded
- Detection: The JVM built-in safety valve.
- User or operator impact: The JVM built-in safety valve.
OutOfMemoryError: Metaspace
- Detection: The class metadata region is full.
- User or operator impact: The class metadata region is full.
