Why ZGC Pauses Stay Under 1 ms: Inside Colored Pointers and Load Barriers
The article explains how ZGC achieves sub‑millisecond pause times by embedding GC state in pointer bits (colored pointers) and using JIT‑injected load barriers that fix references on first access, comparing this approach with G1 and Shenandoah and detailing ZGC 2.0 enhancements and known pitfalls.
1. The fundamental problem: what to do with references after object relocation?
All garbage collectors face moving objects while application threads may still hold old addresses. G1 solves this by stopping all threads (STW) and updating references, which makes pause time grow with heap size. Shenandoah avoids STW by adding an indirection layer (Brooks Pointer), but every access incurs an extra hop. ZGC takes a different route: it never pauses the application and does not slow every access, fixing references only once.
2. Step one – Colored Pointers
ZGC runs only on 64‑bit systems. It uses the unused high bits of a 64‑bit pointer (bits 42‑45) to store GC metadata. The diagram shows four reserved bits for states Marked0, Marked1, Remapped, and Finalizable. A pointer therefore acts as both an address and a state machine.
63 47 46 45 44 43 42 41 0
┌─────────────────────┬──┬──┬──┬──┬──┬──────────────────┐
│ unused │F │R │M1│M0│ │ real address │
└─────────────────────┴──┴──┴──┴──┴──┴──────────────────┘
↑ metadata bits (4 bits)Marked0 : object is marked in the current cycle (alive).
Marked1 : leftover mark from the previous cycle.
Remapped : object has been moved; its reference needs fixing.
Finalizable : object can only be accessed through its finalizer.
ZGC also splits the virtual address space into three views – Remapped (default), M0 (working view during marking), and M1 (stand‑by view for the next marking round). The active view is selected by the color bits.
Space‑for‑time at its extreme: instead of copying data, it copies address mappings – zero‑copy, pure virtual‑space operation.
3. Step two – Load Barrier
When a thread reads a reference from the heap, the JIT inserts a tiny piece of code – the load barrier – that checks the pointer’s color.
Object o = obj.fieldA; // triggers load barrier (heap read)
Object p = o; // no trigger (register copy)
o.doSomething(); // no trigger (no heap read)
int i = obj.fieldB; // no trigger (not an object reference)During the concurrent marking phase the barrier:
If the color is Remapped, it changes it to Marked0 and enqueues the object for marking.
If the color is already Marked0 or Marked1, it takes the fast path and does nothing.
During the concurrent relocation phase the barrier:
If the color is Marked0/Marked1, it looks up the forwarding table, rewrites the pointer in place to the new address, and sets the color to Remapped.
If the color is already Remapped, it follows the fast path with zero overhead.
This “pointer self‑healing” means the first access fixes the reference; subsequent accesses run at full speed.
Key design: why two Marked bits? Because marking is concurrent; the second bit avoids re‑enqueuing the same reference.
Comparison with Shenandoah’s Brooks Pointer:
First access: both slow (ZGC – barrier fix; Shenandoah – extra indirection).
Later accesses: ZGC fast (fast path, zero cost); Shenandoah remains slow (extra hop).
Throughput loss: ZGC 5‑10 % vs. continuous loss for Shenandoah.
4. Full GC cycle script
Assuming a full heap, the GC proceeds as:
[STW ①] Initial marking (~0.1 ms)
Scan roots, set their colors to Marked0
[Concurrent marking]
Application threads run; each heap read triggers the load barrier.
Unmarked objects become Marked0 and are queued; already marked follow fast path.
[STW ②] Final marking (~0.1 ms)
Handle remaining SATB issues.
[Concurrent relocation] ★ core magic
GC copies live objects to a new region.
When a thread reads an old address, the barrier:
1. Looks up the forwarding table.
2. Rewrites the reference in place.
3. Changes the color to Remapped.
The reference is now fixed; no global STW needed.
[STW ③] Cleanup (~0.1 ms)
Free the old region.The three STW pauses each stay under 1 ms, totaling about 0.3 ms regardless of heap size – even a 16 TB heap stays within the 1 ms budget.
5. ZGC 2.0 (JDK 25) upgrades
JDK 25 introduces ZGC 2.0 with several enhancements:
Maximum heap increased from ≤16 TB to ≤64 TB (stable in tests).
Class unloading becomes fully concurrent, eliminating STW.
99.9 % latency drops from <10 ms to <1 ms for heaps ≤32 GB.
Load‑barrier overhead reduced by ~40 % thanks to hardware assistance (CET on x86, SVE2 on ARM).
Hardware assistance turns the color‑bit check from a memory‑access plus branch into a pure register‑bit operation, cutting latency.
ZGC 2.0 also enables concurrent class unloading by default, preventing Full‑GC fallback caused by metaspace pressure.
Enabling ZGC remains a single JVM flag:
java -XX:+UseZGC -Xmx16g MyApp.jar
# All ZGC features are on by default6. Pitfalls you must know
Compressed oops must be disabled ( -XX:-UseCompressedOops) or the high bits used for colors are lost.
Only 64‑bit platforms are supported; 32‑bit lacks enough bits for colors.
JNI code must avoid GetPrimitiveArrayCritical and direct pointer arithmetic, which bypass the load barrier and can read stale data.
ZGC trades 5‑10 % throughput for latency; high‑throughput batch workloads may perform better with G1.
7. The design philosophy
ZGC’s “pain once, enjoy forever” philosophy means the application pays a small cost on the first access to a moved object, after which all accesses are fast. This is why ZGC can claim sub‑millisecond pauses even with terabyte‑scale heaps.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer1970
Formerly called 'Code to 35'. Add our main WeChat ID to access a wealth of shared resources (algorithms, interview prep, tech stacks: Java, Python, Go, big data). We mainly share serious development techniques, focusing on output-driven input. Occasionally we post life snippets and gossip. Our aim is to attract precise traffic and test advertising opportunities.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
