Under The Hood
backend java JDK 21+ (G1, ZGC, Shenandoah)

Garbage Collectors: Generational G1 vs ZGC Colored Pointers

Last updated
Prerequisites:
JVM memory model basics (heap, regions)
Understanding of GC concepts (mark, sweep, compact)
  • java
  • garbage-collection
  • g1
  • zgc
  • shenandoah
  • jvm
  • memory

Read at your depth

The practical view

G1 is the default GC since JDK 9: it divides the heap into regions and collects incrementally, prioritizing regions with the most garbage (garbage-first). Use -XX:+UseG1GC and tune with -XX:MaxGCPauseMillis. ZGC (-XX:+UseZGC) is a low-latency collector designed for very large heaps (up to terabytes): it keeps pause times under ~1ms regardless of heap size by doing most work concurrently with the application. Shenandoah (-XX:+UseShenandoahGC) is similar (OpenJDK, also in Red Hat builds). Choose G1 for general throughput, ZGC/Shenandoah for latency-critical services with large heaps.

The same idea in other frameworks

react equivalent
N/A — JVM runtime concern.

Legacy vs modern

G1 default config vs ZGC low-latency config

G1 targets bounded pauses with default settings; ZGC trades CPU/memory for near-zero pauses on large heaps.

before → after
Untuned G1 for a latency-critical service
java -Xms4g -Xmx4g -jar app.jar # ❌ OLD: default G1, pauses can spike on large heaps
ZGC for low-latency large heap
java -XX:+UseZGC -Xms16g -Xmx16g -jar app.jar # ✅ NEW: sub-ms pauses at any heap size

Interview gotchas

Context

This separates candidates who understand the load-barrier/colored-pointer design from those who just know 'ZGC is low latency' — interviewers want the mechanism that removes heap-size dependency.

The mechanical answer

ZGC's pause time is bounded by the number of GC roots (thread stacks, registers, JNI roots), not by heap size, because all the heap-wide work happens concurrently with the application. The mechanism is the load barrier + colored pointers: the GC encodes per-region state (marked0/marked1/remapped/finalizable) in the unused high bits of 64-bit object references. When application threads load an object reference, the load barrier checks the color bits and, if the region is being remapped/relocated, adjusts the pointer (or reads the forwarding address) on the spot. Marking and relocation are therefore incremental and concurrent — the app threads cooperate through barriers rather than stopping. G1, by contrast, performs evacuation with a stop-the-world pause whose duration grows with the live-set size being copied and the remembered-set scanning work.

Trap

The naive answer is 'ZGC uses more CPU so it's faster'. The real distinction is the concurrency model: barrier-assisted concurrent relocation vs stop-the-world evacuation. Another trap: thinking ZGC works on any heap size for free — it needs enough memory for the multi-mapped/forwarding overhead, and generational ZGC is recommended for typical workloads to reduce allocation-rate overhead. Candidates who mention colored pointers and load barriers show real understanding.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.