Skip to main content
Articles/Java/how-jvm-garbage-collection-works
Series: Java Backend Modernization•Part 6 of 17
Scenario Article

How JVM Garbage Collection Works: Types, Memory Impact, and Tuning

Diagnosing a GC Problem: A Step-by-Step Production Walkthrough

Abstract Algorithms•Apr 10, 2026
💡 Executive Summary

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

1

Stage 1 — When the JVM Stops the World: A Production Incident

Action / Decision:

Your payment microservice is paging the on-call engineer.

Component Behavior:

P99 latency has spiked from 180 ms to 34,000 ms.

Observable Output: The load balancer is timing out requests.
Read Full Stage in Deep Dive ↓
📖

Deep Dive

Detailed Architectural Analysis

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.

Share this case study

Discuss architecture trade-offs and lessons with your team.

Share:XLinkedInReddit