What is ZGC
The Z Garbage Collector (ZGC) is a scalable, ultra-low latency Garbage Collector designed to manage heaps ranging from a few megabytes to 16 Terabytes while guaranteeing sub-mil...
🟢 Junior Level
The Z Garbage Collector (ZGC) is a scalable, ultra-low latency Garbage Collector designed to manage heaps ranging from a few megabytes to 16 Terabytes while guaranteeing sub-millisecond ($< 1.0\text{ ms}$) Stop-The-World (STW) pause times, regardless of total heap size.
Real-World Analogy
Think of road repairs in a bustling metropolitan city:
- Traditional road maintenance (G1 or Parallel GC) requires shutting down entire multi-lane highways during rush hour (STW pauses) so bulldozers can repave and relocate lanes.
- ZGC operates like an automated nighttime conveyor system: traffic continues flowing across all lanes at 60 mph while automated robotic crews repave and relocate lanes beneath moving vehicles (Concurrent Evacuation). If a driver approaches a lane segment that was just moved, automated digital signs instantaneously redirect the car to the new lane in a fraction of a microsecond (Load Barrier & Self-Healing).
Why ZGC Pause Times Do Not Scale With Heap Size
In traditional collectors (Parallel GC, CMS, and even G1), pause times scale with the volume of live data that must be evacuated and compacted.
ZGC eliminates this dependency:
- Only Root Scanning Pauses: The only operations executed inside a Stop-The-World pause are initial thread-stack scanning and safepoint handshakes, which depend strictly on the number of active threads, not on heap size.
- All Heavy Phases Are Concurrent: Object marking, reference verification, memory evacuation (relocation), and pointer updating occur concurrently on background threads while application code runs unimpeded.
┌────────────────────────────────────────────────────────┐
│ G1 GC vs. ZGC Latency │
├────────────────────────────────────────────────────────┤
│ G1 GC Pause: [ 20ms ................. 200ms ] (STW) │
│ ZGC Pause: [ < 1ms ] (Constant across 16 TB Heaps!) │
└────────────────────────────────────────────────────────┘
The Trade-off: To achieve $<1\text{ ms}$ pauses, ZGC introduces JIT-compiled Load Barriers on object reference reads, sacrificing 5%–15% of raw application throughput compared to Parallel GC or G1.
🟡 Middle Level
The Three Architectural Pillars of ZGC
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ 1. Colored Pointers │
│ Encodes GC metadata directly into the upper bits (42–45) of 64-bit reference pointers│
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ 2. Load Barriers & Self-Healing │
│ Intercepts reference reads; if an object was relocated, automatically heals pointer │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ 3. Concurrent Evacuation (Relocation) │
│ Copies live objects to clean pages in parallel while application threads execute │
└─────────────────────────────────────────────────────────────────────────────────────────┘
1. Colored Pointers & The Compressed OOPs Limitation
On 64-bit hardware, ZGC utilizes the reference pointer’s upper bits as metadata tags:
64-Bit Reference Pointer in ZGC:
+-------------------+-------------+-----------------------------------------------+
| 16 Unused Bits | 4 Color Bits| 44 Address Bits (Addresses up to 16 Terabytes)|
+-------------------+-------------+-----------------------------------------------+
| | | |
| | | +-- Finalizable
| | +---- Remapped (Pointer is clean and points to new address)
| +------ Marked1
+-------- Marked0
- In-Pointer Metadata: ZGC checks whether an object is alive or relocated directly from the pointer itself, without performing costly cache-line dereferences into heap memory.
- No Compressed OOPs: Because ZGC requires 64-bit pointers to store color bits, it cannot use Compressed OOPs (
-XX:+UseCompressedOops). On heaps $< 32\text{ GB}$, every object reference in ZGC consumes 8 bytes rather than 4 bytes, increasing overall heap footprint.
2. Load Barriers & Self-Healing
Unlike G1 (which intercepts reference writes), ZGC intercepts reference reads (Ref r = container.field):
Application Thread reads reference: `Person ceo = company.getCeo();`
│
▼
JIT Load Barrier checks `Remapped` color bit
│
┌──────────────┴──────────────┐
▼ (Bit is set) ▼ (Bit is NOT set)
[ Pointer is valid! ] [ Object has moved to new page! ]
(Returns in zero cycles) │
▼
Lookup new address in Forwarding Table
│
▼
Update `company.ceo` via atomic CAS
│
▼
Return updated address (Self-Healed!)
The thread that encounters an un-remapped pointer updates the reference in place (Self-Healing). All subsequent reads to that field by any thread execute instantaneously with zero overhead.
Architectural Comparison: G1 GC vs. ZGC
| Feature | G1 GC | ZGC (Generational) |
|---|---|---|
| Typical STW Pause | 20 ms – 200 ms (Soft target) | $< 1.0\text{ ms}$ (Guaranteed) |
| Heap Size Scaling | Pauses increase with heap size | Pauses independent of heap size |
| Supported Heap Size | 2 GB to 64 GB typical | Up to 16 Terabytes |
| JIT Barriers | Write Barriers (Pointer mutations) | Load Barriers (Pointer reads) |
| Throughput Tax | Low (~1–3%) | Medium (~5–15% CPU) |
| Compressed OOPs | Supported ($< 32\text{ GB}$) | Not Supported (Strictly 64-bit) |
🔴 Senior Level
Generational ZGC (Java 21+ JEP 439)
From Java 11 through Java 17, ZGC was single-generational:
- Background GC threads were forced to scan the entire 16 TB heap uniformly on every cycle.
- Under high allocation rates, single-generational ZGC could not reclaim memory fast enough, resulting in Allocation Stalls: application mutator threads attempting to allocate objects were blocked in emergency wait loops.
The Java 21 Revolution: Generational ZGC
Introduced in Java 21 (-XX:+UseZGC -XX:+ZGenerational), ZGC partitions the heap into Young and Old generations:
- The Young Generation is collected concurrently at high frequency, recycling ephemeral objects before they ever reach Old Gen.
- Delivers a 4x reduction in CPU allocation stalls and slashes GC background CPU usage by up to 50%.
Virtual Memory Multi-Mapping
Because colored pointers manipulate bits 42–45, passing these modified addresses directly to CPU memory controllers would cause hardware faults if the bits pointed to unmapped address ranges.
To resolve this without requiring expensive CPU bitmask instructions on every memory access, ZGC uses OS Page Multi-Mapping (mmap on Linux):
- The JVM maps the exact same physical memory frame into three distinct virtual address spaces (corresponding to
Marked0,Marked1, andRemapped). - Whether a colored pointer has the
Marked0bit or theRemappedbit enabled, the hardware memory management unit (MMU) resolves the virtual address to the exact same physical RAM cell!
Virtual Space (Marked0) ──┐
Virtual Space (Marked1) ──┼──> Same Physical RAM Page in Hardware
Virtual Space (Remapped) ──┘
Production Tuning & Profiling for ZGC
# Enable Generational ZGC (Java 21+)
-XX:+UseZGC -XX:+ZGenerational
# Allocate concurrent GC worker threads (default = CPU Cores / 4)
-XX:ConcGCThreads=4
# Elastic heap management: sets soft ceiling for container elasticity
-XX:SoftMaxHeapSize=12g -Xmx16g
# Verbose ZGC logging
-Xlog:gc*,gc+phases=debug:file=/var/log/zgc.log:time,uptime,pid:filecount=5,filesize=100M
[!TIP] When NOT to use ZGC:
- CPU-Bound Workloads: If your application runs at 85–95% CPU utilization, ZGC’s Load Barriers will compete for CPU cycles and degrade throughput.
- Small Memory Heaps ($< 4\text{ GB}$): G1 GC uses memory more efficiently due to Compressed OOPs.
- Non-Interactive Batch Pipelines: Parallel GC completes large data processing jobs 15–20% faster by omitting runtime barrier overhead.
4 Tricky Questions
1. How does ZGC maintain sub-millisecond Stop-The-World pause times regardless of whether the heap is 10 GB or 16 TB?
Answer: In traditional collectors, the longest STW pause occurs during object evacuation and memory compaction, where surviving objects are physically relocated and their pointer references updated across the heap. ZGC executes both object evacuation and pointer updating concurrently in the background while application threads continue executing. The only phases executed during ZGC’s STW pauses are initial thread stack scanning (scanning local variable roots) and safepoint transitions. Because thread stack depth depends on application concurrency rather than total heap memory, ZGC pauses remain constant at $<1\text{ ms}$ whether running on a 10 GB or 16 TB heap.
2. What are Colored Pointers in ZGC, and why does ZGC not support Compressed OOPs?
Answer: Colored Pointers are 64-bit reference addresses that embed GC tracking metadata directly into the upper 4 bits (bits 42 to 45: Marked0, Marked1, Remapped, Finalizable) of the memory pointer. This design enables the Garbage Collector and Load Barriers to determine an object’s GC state and relocation status simply by inspecting the reference address, without dereferencing the object in heap memory. Because ZGC requires a full 64-bit address space to manipulate these metadata bits, it is physically impossible to compress pointers into 32-bit representations. Consequently, ZGC does not support Compressed OOPs, meaning object references consume 8 bytes on all heap sizes.
3. How does the Self-Healing mechanism in ZGC’s Load Barriers work during concurrent object evacuation?
Answer: When ZGC evacuates an object concurrently, it copies the object to a new memory page and registers its old and new addresses in a Forwarding Table. When an application mutator thread reads a reference to that object (ref = container.field), the JIT-compiled Load Barrier checks the pointer’s Remapped color bit. If the bit is not set, the thread intercepts the read, queries the Forwarding Table to obtain the object’s new memory location, and performs an atomic CAS operation to overwrite the reference stored in container.field with the new address. This is called Self-Healing: the first thread to read the un-evacuated pointer repairs it in-memory so that subsequent reads bypass the barrier in zero cycles.
4. Why did Oracle introduce Generational ZGC in Java 21, and what critical flaw of single-generational ZGC did it resolve?
Answer: Single-generational ZGC (Java 11–17) had to scan the entire heap uniformly on every cycle, violating the Weak Generational Hypothesis (that most objects die young). In applications with high allocation rates, background GC threads could not mark and evacuate the whole multi-terabyte heap fast enough before free memory ran out. This triggered Allocation Stalls, where allocating threads were forcibly suspended until the concurrent cycle finished, causing severe latency spikes. Generational ZGC (Java 21 JEP 439) partitions the heap into Young and Old generations, collecting the Young Generation frequently and cheaply. This reduced background CPU consumption by up to 50% and virtually eliminated Allocation Stalls under high allocation churn.
🎯 Interview Cheat Sheet
30-Second Elevator Pitch
“ZGC is an ultra-low latency, scalable garbage collector that delivers sub-millisecond ($< 1\text{ ms}$) Stop-The-World pauses across heaps up to 16 Terabytes. Its architecture rests on three pillars: Colored Pointers (storing GC metadata in the upper bits of 64-bit addresses), Load Barriers with Self-Healing (transparently updating relocated object references on read), and Concurrent Evacuation. In Java 21, Generational ZGC solved the allocation stall problem of early versions, halving CPU consumption. ZGC trades 5–15% CPU throughput and disables Compressed OOPs to achieve unparalleled latency consistency for financial trading, gaming, and strict SLA services.”
Key Architectural Takeaways
- Guaranteed Latency: Pauses remain $< 1.0\text{ ms}$ regardless of heap scale (from 16 MB to 16 TB).
- Colored Pointers: Bits 42–45 store GC metadata (
Marked0,Marked1,Remapped); requires 64-bit architecture. - No Compressed OOPs: Object references always consume 8 bytes, even on heaps $< 32\text{ GB}$.
- Load Barriers & Self-Healing: Operates on pointer reads; mutators automatically update stale references in place.
- Generational ZGC (Java 21): Adds generational separation to ZGC, halving CPU overhead and eliminating allocation stalls.
- Virtual Memory Multi-Mapping: Maps the same physical RAM page across three virtual address ranges to prevent MMU faults.
Red Flags & Anti-Patterns
- 🚩 Claiming “ZGC uses Write Barriers like G1”: ZGC’s core innovation is JIT-compiled Load Barriers on pointer reads.
- 🚩 Assuming ZGC supports Compressed OOPs: Colored Pointers strictly require full 64-bit reference pointers.
- 🚩 Using ZGC for CPU-bound batch workloads: The 5–15% Load Barrier tax reduces batch computation throughput compared to Parallel GC.
- 🚩 Believing ZGC pause times grow with heap size: Only thread stacks are scanned during pauses; pause duration is independent of heap capacity.
- 🚩 Deploying single-generational ZGC on high-churn Java 17 systems: Risks catastrophic Allocation Stalls; upgrade to Java 21 Generational ZGC.
Related Topics
- What GC algorithms exist — Algorithmic strategies and collector taxonomy
- What is G1 GC — Region-based collection comparison
- What is Shenandoah GC — Alternative low-latency collector using Brooks pointers
- Which GCs minimize stop-the-world pauses — Comprehensive low-latency benchmark analysis
- What is stop-the-world — Safepoints and thread suspension mechanics