Back to CI/CD Pipeline
Q6Question 6 of 56

How ZGC achieves sub-millisecond pauses: colored pointers, load barriers, concurrent relocation, self-healing references: and what Generational ZGC is

Key Concepts
  • ZGC keeps pauses under a millisecond regardless of heap size by doing marking and relocation concurrently with the application.
  • Colored pointers store GC metadata (Marked, Remapped) in spare bits of the 64-bit reference.
  • Load barriers check the color on every reference load; if the object has moved, the barrier transparently fixes the reference.
  • Self-healing means each reference is corrected at most once per cycle, spreading the cost smoothly instead of one long pause.
  • Generational ZGC splits the heap into young and old generations so most work targets short-lived objects.
  • Generational ZGC was opt-in in Java 21, default in Java 23, and the only mode in Java 24 via JEP 490.

Definition

ZGC keeps pauses under a millisecond by marking and moving objects concurrently, using colored pointers plus load barriers so references self-heal to the new location on first read.
  • ZGC – a scalable, low-latency, mostly-concurrent garbage collector whose stop-the-world pauses stay under a millisecond regardless of heap size.
  • Colored pointers – GC metadata stored in spare bits of the 64-bit reference (Marked, Remapped, generation bits).
  • Load barriers – small JIT-inserted code on every reference load that checks the color bits and fixes up relocated references.
  • Self-healing references – the property that each reference is corrected at most once per cycle on first touch.
  • Concurrent relocation – ZGC compacts the heap while the application runs, using load barriers to lazily repoint references.
  • Generational ZGC – young/old generation split for efficiency (opt-in Java 21, default Java 23, exclusive Java 24).

Imagine renovating the bank while customers keep transacting. The crew moves one desk at a time in the background. They tag each file with a coloured sticker that says this file is being moved. When a teller picks up a file and sees the sticker, the teller instantly updates their note to the new location and carries on. The coloured stickers are colored pointers, the teller's instant update is the load barrier and self-healing, and the one-desk-at-a-time move is concurrent relocation.

Understanding the Concepts

What problem does ZGC solve?

Traditional collectors pause the entire application to move objects. As the heap grows, the pause grows. ZGC eliminates that correlation: pauses stay under a millisecond even on multi-terabyte heaps.

BASH
# Enable ZGC
java -XX:+UseZGC -Xmx64g -jar fraud-scoring.jar

# GC log shows sub-millisecond pauses
# [0.234s] GC(0) Pause Mark Start 0.015ms
# [0.345s] GC(0) Pause Mark End 0.012ms
# [0.456s] GC(0) Pause Relocate Start 0.008ms

How do colored pointers work?

On a 64-bit JVM, a heap reference does not need all its bits for the address. ZGC steals a few bits to store GC state. The address bits still point at the object; the color bits tell ZGC what phase-relevant work is pending.

DIAGRAM
64-bit reference layout in ZGC:
┌──────────────────────────────────────────────────────────────┐
│ Unused │ Finalizable │ Remapped │ Marked1 │ Marked0 │ Address│
│  bits  │    1 bit    │  1 bit   │  1 bit  │  1 bit  │ 44 bits│
└──────────────────────────────────────────────────────────────┘

The color bits encode the GC phase:
  Marked0/1 : object has been reached during marking
  Remapped   : reference points to the object's current location
  Finalizable: object is only reachable through a finalizer

How do load barriers and self-healing work?

Load barriers are small snippets of code the JIT inserts on every reference load. When the application reads a reference, the barrier checks the color bits.

TEXT
Pseudocode of a ZGC load barrier:

  Object ref = loadFromMemory(address);
  if (ref.colorBits != CURRENT_GOOD_COLOR) {
      ref = slowPath_fixup(ref);  // look up new address, rewrite
  }
  // ref now points to current location
JAVA
package com.bank.service;

import java.math.BigDecimal;

public class FraudScoringService {

    // Under ZGC, every reference load has a barrier
    // but the cost is near-zero when the color is current
    private final TransactionGraph graph; // large, long-lived

    public FraudScoringService(TransactionGraph graph) {
        this.graph = graph;
    }

    public BigDecimal scoreTransaction(String txnId) {
        // This reference load goes through the load barrier
        // If the graph was relocated, the barrier fixes the ref
        TransactionNode node = graph.getNode(txnId);
        return node.calculateRiskScore();
    }
}

How does concurrent relocation work?

ZGC selects pages that are mostly garbage, copies the few live objects out to fresh pages, and lets the load barriers lazily repoint any references that still point at the old location.

DIAGRAM
Concurrent relocation:
  Before: [Page-A: 10% live, 90% garbage] [Page-B: 80% live]

  ZGC picks Page-A (most garbage):
  1. Copy live objects from Page-A to fresh Page-C (concurrent)
  2. Any reference to old-Page-A objects → load barrier fixes it
  3. Page-A is freed

  After: [Page-C: relocated live] [Page-B: unchanged] [Page-A: FREE]

What is Generational ZGC?

Generational ZGC adds young and old generations. Since most objects die young, collecting the young generation frequently and cheaply reclaims most garbage without scanning the whole heap.

BASH
# Java 21-22: opt-in generational mode
java -XX:+UseZGC -XX:+ZGenerational -Xmx64g -jar app.jar

# Java 23: generational is default
java -XX:+UseZGC -Xmx64g -jar app.jar

# Java 24: non-generational removed (JEP 490)
# ZGC is always generational
java -XX:+UseZGC -Xmx64g -jar app.jar
TEXT
Generational ZGC timeline:
  Java 15: ZGC production-ready (single-generation)
  Java 21: Generational ZGC opt-in (-XX:+ZGenerational)
  Java 23: Generational ZGC default
  Java 24: Non-generational removed (JEP 490)

How is ZGC different from G1?

| Aspect | G1 | ZGC |
|---|---|---|
| Pause model | Bounded STW (100-200ms) | Sub-millisecond |
| Pause scales with heap? | Yes (somewhat) | No |
| Mechanism | Evacuate selected regions (STW) | Concurrent relocation + load barriers |
| Max practical heap | ~64 GB | Multi-terabyte |
| Throughput overhead | Low | Moderate (barriers + concurrent work) |
| Default since | Java 9 | Opt-in (Java 15+) |

What are common mistakes?

The most common mistake is enabling ZGC without providing enough heap headroom for concurrent allocation.

BASH
# WRONG: ZGC with heap too tight for concurrent work
java -XX:+UseZGC -Xmx2g -jar high-throughput-service.jar
# May cause allocation stalls!

# CORRECT: give ZGC headroom
java -XX:+UseZGC -Xms8g -Xmx8g -XX:+AlwaysPreTouch -jar app.jar

What should a senior engineer know?

A senior engineer monitors the allocation-stall count and pause percentile on the dashboard. At National Bank of India, ZGC was used for the real-time fraud-scoring service that held a multi-gigabyte transaction graph in memory and had to answer the payment switch within a tight latency budget.

BASH
# Monitor ZGC health
java -Xlog:gc*:file=zgc.log:time,level,tags \
     -XX:+UseZGC -Xms64g -Xmx64g -XX:+AlwaysPreTouch \
     -jar fraud-scoring.jar

# Check for allocation stalls
grep "Allocation Stall" zgc.log

Visual Overview

DIAGRAM
  ZGC concurrent cycle:
  ──────────────────────────────────────────────────
  Application threads keep running throughout

  Phase 1: Pause Mark Start (< 1ms STW)
           Root scanning

  Phase 2: Concurrent Mark
           Walk object graph, mark live objects
           (application runs, load barriers active)

  Phase 3: Pause Mark End (< 1ms STW)
           Finalize marking

  Phase 4: Concurrent Relocate
           Copy live objects from garbage-rich pages
           to fresh pages (application runs)
           Load barriers fix references lazily

  Phase 5: Pause Relocate Start (< 1ms STW)
           Update root references

  Total STW: < 1ms regardless of heap size
  ──────────────────────────────────────────────────

  Colored pointer:
  ┌─────────┬──────────┬─────────┬─────────────────┐
  │ unused  │ Remapped │ Marked  │  44-bit address  │
  └─────────┴──────────┴─────────┴─────────────────┘

Key Takeaway

ZGC achieves sub-millisecond pauses by doing the expensive work: marking and relocation, concurrently with the application. Colored pointers store GC metadata in spare bits of the 64-bit reference. Load barriers check the color on every reference load, and if the object has moved, they transparently fix the reference. That self-healing means each reference is corrected at most once per cycle, spreading the cost smoothly.

Generational ZGC adds young and old generations so most collection targets short-lived objects without re-scanning the whole heap. It was opt-in in Java 21, default in Java 23, and the only mode in Java 24.

At National Bank of India, ZGC kept the fraud-scoring service's 99th-percentile pause under a millisecond on a 64 GB heap holding a live transaction graph, while the generational young collections absorbed the heavy churn of short-lived scoring contexts.

Remember one sentence: ZGC's colored pointers and load barriers let it move objects while the application runs, so pauses stay under a millisecond no matter how large the heap grows.

---

Share
Use ← → arrow keys to navigate