Java ZGC Interview Questions and Answers | Low-Latency GC, Generational ZGC, Colored Pointers and Production Tuning
Master Java ZGC with real interview questions, low-latency architecture, colored pointers, load barriers, concurrent relocation, Generational ZGC, JVM tuning, GC logs, containers, and production troubleshooting.
Introduction
The Z Garbage Collector, commonly called ZGC, is a scalable, low-latency Garbage Collector designed for applications where long Garbage Collection pauses are unacceptable.
Traditional collectors perform significant amounts of marking, object relocation, and compaction while application threads are stopped.
ZGC takes a different approach.
It performs most expensive Garbage Collection work concurrently with application threads, including:
- Object marking
- Reference processing
- Object relocation
- Heap compaction
- Reclaiming unused memory
Application threads are paused only during a few short phases.
ZGC is designed so that pause time does not grow significantly with:
- Heap size
- Number of live objects
- Application allocation volume
OpenJDK describes ZGC as a scalable low-latency collector capable of supporting heaps from megabytes to multiple terabytes while targeting sub-millisecond maximum pauses.
ZGC is particularly useful for:
- Payment processing systems
- Trading platforms
- Low-latency APIs
- Large in-memory caches
- Search platforms
- Real-time analytics
- Large Spring Boot applications
- Systems with strict latency service-level objectives
Learning Objectives
After completing this article, you will understand:
- What ZGC is
- Why ZGC was introduced
- ZGC architecture
- Concurrent marking
- Concurrent relocation
- Colored pointers
- Load barriers
- Store barriers
- Relocation sets
- Forwarding tables
- Heap regions
- Generational ZGC
- Young and old generations
- Minor and major collections
- JVM options
- Heap sizing
- Allocation stalls
- ZGC logs
- Container considerations
- Production troubleshooting
- G1GC versus ZGC
- Senior-level interview questions
Why Was ZGC Introduced?
Large Java heaps create challenges for traditional Garbage Collectors.
As the heap and live object set grow, Stop-The-World operations can cause:
- Long response-time spikes
- Request timeouts
- Failed health checks
- Kafka consumer lag
- Missed trading windows
- Poor user experience
- Unstable tail latency
G1GC reduces long pauses by collecting the heap incrementally, but object evacuation still happens primarily during Stop-The-World pauses.
ZGC was designed to move most expensive GC work out of those pauses.
Its main goals are:
- Very short pause times
- Pause times that remain relatively independent of heap size
- Support for very large heaps
- Concurrent heap compaction
- Minimal manual tuning
- Predictable latency
ZGC was introduced experimentally in JDK 11 and became a production feature in JDK 15.
Enabling ZGC
Use:
java -XX:+UseZGC -jar application.jar
For JDK 21, Generational ZGC can be explicitly enabled with:
java \
-XX:+UseZGC \
-XX:+ZGenerational \
-jar application.jar
Generational ZGC was introduced in JDK 21.
In JDK 23, generational mode became the default ZGC mode.
In JDK 24, the non-generational implementation was removed, so:
-XX:+UseZGC
selects Generational ZGC without requiring -XX:+ZGenerational.
Verify the Active Collector
Use:
java -Xlog:gc -XX:+UseZGC -version
For a running process:
jcmd <pid> VM.flags
You can also inspect startup flags:
java -XX:+PrintCommandLineFlags -XX:+UseZGC -version
High-Level ZGC Architecture
flowchart TD
App["Application Threads"] --> Heap["ZGC Heap"]
Heap --> Mark["Concurrent Marking"]
Mark --> Select["Select Relocation Set"]
Select --> Relocate["Concurrent Relocation"]
Relocate --> Reclaim["Reclaim Memory"]
Reclaim --> Heap
App --> Barrier["Load and Store Barriers"]
Barrier --> Mark
Barrier --> Relocate
The important difference is that application threads continue running during most of the GC cycle.
ZGC Core Characteristics
ZGC is:
- Concurrent
- Region-based
- Compacting
- Generational in modern JDKs
- NUMA-aware
- Designed for low latency
- Based on pointer metadata and barriers
- Capable of concurrent object relocation
The original ZGC design was non-generational. Modern ZGC separates recently created and long-lived objects into young and old generations to reduce collection work and improve application performance.
ZGC Heap Architecture
ZGC divides the heap into logical regions called ZPages.
Depending on the JDK implementation and object size, ZGC can use page categories appropriate for:
- Small objects
- Medium objects
- Large objects
Conceptually:
+-------------+-------------+-------------+-------------+
| Young Page | Old Page | Free Page | Young Page |
+-------------+-------------+-------------+-------------+
| Old Page | Large Object Page | Free Page |
+-------------+---------------------------+-------------+
Unlike traditional contiguous generation layouts, ZGC manages heap memory through independently reclaimable pages.
Object Allocation Flow
With Generational ZGC, newly created objects normally begin in the young generation.
flowchart LR
Create["Create Object"] --> Young["Young Generation"]
Young --> Minor["Young Collection"]
Minor --> Dead["Dead Object Reclaimed"]
Minor --> Survive["Object Survives"]
Survive --> Promote["Promote to Old Generation"]
Promote --> Old["Old Generation"]
Most Java objects die shortly after allocation.
Generational ZGC takes advantage of this behavior by collecting the young generation more frequently and the old generation less frequently.
Why Generational ZGC?
The original ZGC treated the heap largely as one generation.
That approach provided excellent latency but could perform unnecessary work because recently created and long-lived objects were processed together.
Generational ZGC divides the heap into:
- Young generation
- Old generation
This allows ZGC to:
- Collect short-lived objects more frequently
- Avoid scanning the complete old generation during every young collection
- Reduce marking work
- Improve allocation performance
- Reduce GC CPU overhead
- Improve overall throughput
flowchart TD
Heap["ZGC Heap"] --> Young["Young Generation"]
Heap --> Old["Old Generation"]
Young --> Frequent["Frequent Young Collections"]
Old --> Infrequent["Less-Frequent Old Collections"]
Frequent --> Promote["Promote Surviving Objects"]
Promote --> Old
Minor and Major Collections
In Generational ZGC, collections can focus on different portions of the heap.
Minor Collection
A minor collection primarily processes the young generation.
Its purpose is to:
- Reclaim short-lived objects
- Identify surviving objects
- Promote sufficiently long-lived objects
- Keep young-generation allocation available
Major Collection
A major collection performs broader work involving both generations, including old-generation marking and reclamation.
Major collections occur less frequently because old objects generally survive longer.
flowchart LR
Minor["Minor Collection"] --> Young["Young Generation"]
Major["Major Collection"] --> YoungOld["Young and Old Generations"]
Stop-The-World Phases
ZGC is not completely pause-free.
It still requires short Stop-The-World phases for operations such as:
- Initial root processing
- Final root processing
- Synchronizing certain collection transitions
The expensive work remains concurrent.
flowchart LR
Run1["Application Running"] --> Pause1["Short Pause"]
Pause1 --> Concurrent["Concurrent GC Work"]
Concurrent --> Pause2["Short Pause"]
Pause2 --> Run2["Application Continues"]
ZGC pause times are designed not to increase significantly with heap size because the long-running heap work happens concurrently.
ZGC Collection Lifecycle
A simplified ZGC cycle looks like this:
flowchart TD
Start["Start Collection"] --> MarkStart["Pause: Mark Start"]
MarkStart --> Mark["Concurrent Mark"]
Mark --> MarkEnd["Pause: Mark End"]
MarkEnd --> Prepare["Prepare Relocation Set"]
Prepare --> Relocate["Concurrent Relocation"]
Relocate --> Reclaim["Reclaim Pages"]
Reclaim --> Complete["Collection Complete"]
Modern Generational ZGC contains additional coordination between young and old-generation cycles, but the main design remains centered on concurrent marking and relocation.
Concurrent Marking
During concurrent marking, ZGC identifies objects that are reachable from GC roots while application threads continue running.
GC roots include:
- Thread stack references
- Static references
- JNI references
- JVM internal references
- Active class metadata references
flowchart TD
Roots["GC Roots"] --> A["Object A"]
A --> B["Object B"]
B --> C["Object C"]
X["Object X"] --> Y["Object Y"]
C --> Live["Reachable"]
Y --> Garbage["Unreachable"]
Objects reachable from GC roots remain alive.
Unreachable objects are eligible for reclamation.
Concurrent Relocation
Relocation means moving live objects away from sparsely populated heap pages so that the original pages can be reclaimed.
With many collectors, object relocation occurs during a long Stop-The-World pause.
ZGC performs most relocation concurrently.
flowchart LR
Source["Source Page"] --> Live["Live Objects"]
Live --> Move["Concurrent Move"]
Move --> Target["Target Page"]
Source --> Free["Source Page Reclaimed"]
Application threads may access an object while it is being moved.
ZGC resolves this safely using pointer metadata, barriers, and relocation information.
What Are Colored Pointers?
ZGC stores Garbage Collection metadata in object references.
This technique is commonly described as colored pointers.
Conceptually, a pointer contains:
Object Address + GC Metadata
The metadata helps ZGC determine whether a reference is:
- Associated with the current marking state
- In need of remapping
- Referring to relocated data
- Valid for the active collection phase
flowchart LR
Pointer["Object Reference"] --> Address["Object Address"]
Pointer --> Metadata["GC Metadata Bits"]
Metadata --> Mark["Marking State"]
Metadata --> Remap["Remapping State"]
The exact bit layout and implementation details can change between JDK versions.
The important interview concept is that ZGC associates collection-state information with references so application threads can cooperate safely with concurrent Garbage Collection.
What Is a Load Barrier?
A load barrier is a small piece of JVM-generated logic that runs when application code loads an object reference.
Conceptually:
Employee employee = department.getManager();
Before the application uses the loaded reference, the barrier verifies whether the reference points to the current valid location.
flowchart TD
Load["Load Object Reference"] --> Barrier["Load Barrier"]
Barrier --> Valid{"Reference Valid?"}
Valid -->|Yes| Continue["Continue Execution"]
Valid -->|No| Repair["Remap or Repair Reference"]
Repair --> Continue
A load barrier can:
- Detect relocated objects
- Find the object's new location
- Repair stale references
- Allow relocation to remain concurrent
Most loads follow a fast path with minimal additional work.
What Is a Store Barrier?
Generational ZGC also uses barriers when application code stores object references.
Example:
account.setCustomer(customer);
A store barrier helps track references such as:
- Old-generation object to young-generation object
- References modified during concurrent marking
- References relevant to relocation and remembered-set information
flowchart LR
Store["Store Reference"] --> Barrier["Store Barrier"]
Barrier --> Track["Track Reference Relationship"]
Track --> Heap["Update Object Field"]
Store barriers are especially important for a generational collector because the JVM must collect the young generation without scanning every old-generation object.
Load Barrier vs Write or Store Barrier
| Barrier | Trigger | Main purpose |
|---|---|---|
| Load barrier | Reference is loaded | Verify, remap, or repair the reference |
| Store barrier | Reference is written | Track reference updates and cross-generation relationships |
| Marking barrier | Object graph changes | Preserve concurrent-marking correctness |
The implementation may combine responsibilities, but this distinction is useful for interviews.
What Is Self-Healing?
When an application thread encounters an outdated reference, the ZGC barrier can find the object's current location and repair the reference.
This is often called self-healing.
Old reference
|
v
Load barrier detects relocation
|
v
Find new object address
|
v
Repair reference
|
v
Continue application execution
This allows application threads to continue safely even while objects are being relocated.
What Is a Relocation Set?
The Relocation Set contains heap pages selected for evacuation.
Pages with substantial reclaimable memory are candidates.
flowchart TD
Analyze["Analyze Heap Pages"] --> Select["Select Relocation Set"]
Select --> P1["Page 1"]
Select --> P2["Page 2"]
Select --> P3["Page 3"]
P1 --> Move["Move Live Objects"]
P2 --> Move
P3 --> Move
Move --> Reclaim["Reclaim Selected Pages"]
ZGC relocates live objects from selected pages and then returns those pages to the free-memory pool.
What Is a Forwarding Table?
While objects are being relocated, ZGC needs to map an object's old location to its new location.
Conceptually:
Old Address New Address
0x1000 -----> 0x9000
0x1100 -----> 0x9200
0x1200 -----> 0x9400
A forwarding structure enables barriers to resolve references that still point to the old location.
Once references are repaired and relocation information is no longer needed, the associated metadata can be reclaimed.
How ZGC Performs Concurrent Compaction
Compaction removes fragmentation by moving live objects together and freeing sparsely populated memory areas.
Before:
+-------------------------------------+
| Live | Dead | Live | Dead | Live |
+-------------------------------------+
After concurrent relocation:
+-------------------------------------+
| Live | Live | Live | Free |
+-------------------------------------+
ZGC achieves this without stopping the application for the entire relocation process.
That is one of its most important architectural advantages.
Heap Sizing for ZGC
The most important ZGC tuning parameter is:
-Xmx
Example:
-Xmx16g
ZGC needs enough heap for:
- The application's live object set
- New allocations while collection runs concurrently
- Objects being relocated
- Temporary allocation spikes
- Safety headroom
Oracle's ZGC tuning guidance identifies maximum heap size as the most important tuning control. Because ZGC is concurrent, the heap must have sufficient headroom to service allocations while a collection is running.
ZGC Heap Headroom
Suppose an application has:
Maximum heap: 16 GB
Live object set: 13 GB
Free headroom: 3 GB
If the application allocates objects faster than ZGC can reclaim memory, the 3 GB headroom may be consumed before the collection completes.
This can lead to an allocation stall.
A safer configuration may require:
- A larger heap
- A smaller live set
- A lower allocation rate
- More CPU for concurrent GC
- Reduced memory retention
What Is an Allocation Stall?
An allocation stall occurs when an application thread needs memory, but ZGC has not reclaimed sufficient free space.
The application thread must wait until memory becomes available.
flowchart TD
Request["Application Requests Memory"] --> Free{"Free Memory Available?"}
Free -->|Yes| Allocate["Allocate Object"]
Free -->|No| Stall["Allocation Stall"]
Stall --> GC["Wait for ZGC Reclamation"]
GC --> Allocate
Allocation stalls are serious latency events even when normal ZGC pauses remain short.
Common causes include:
- Heap too small
- Live set too large
- Allocation spike
- Insufficient CPU
- GC threads unable to keep up
- Large-object pressure
- Container CPU throttling
SoftMaxHeapSize
ZGC supports a soft heap limit:
-XX:SoftMaxHeapSize=12g
Combined with:
-Xmx16g
this means:
- ZGC attempts to keep heap usage near 12 GB.
- The JVM may use up to 16 GB during pressure.
- The extra space provides temporary headroom.
Example:
java \
-XX:+UseZGC \
-Xms4g \
-Xmx16g \
-XX:SoftMaxHeapSize=12g \
-jar application.jar
SoftMaxHeapSize is a goal rather than a strict limit.
The hard maximum remains Xmx.
Returning Unused Memory to the Operating System
ZGC can uncommit unused heap memory and return it to the operating system under supported conditions.
This can be helpful for applications with changing workloads.
Example:
Morning traffic:
Heap committed = 12 GB
Low-traffic period:
Heap committed reduces toward 6 GB
Whether memory is returned depends on:
- Minimum heap size
- Current live set
- Heap usage
- JVM ergonomics
- Operating-system support
Setting:
-Xms16g
-Xmx16g
may reduce opportunities to uncommit below the minimum heap.
ZGC and Large Objects
Large objects require additional attention because they consume substantial heap capacity quickly.
Example:
byte[] data = new byte[500 * 1024 * 1024];
Potential issues include:
- Rapid exhaustion of heap headroom
- Higher relocation pressure
- Allocation stalls
- OutOfMemoryError
- Increased native and direct-memory pressure when combined with buffers
The best solution is often to avoid creating a single huge object.
For file processing, prefer streaming:
try (InputStream input = file.getInputStream();
BufferedInputStream buffered = new BufferedInputStream(input)) {
byte[] buffer = new byte[16 * 1024];
int count;
while ((count = buffered.read(buffer)) != -1) {
processChunk(buffer, count);
}
}
ZGC vs G1GC
| Feature | ZGC | G1GC |
|---|---|---|
| Primary goal | Extremely low latency | Balance latency and throughput |
| Marking | Mostly concurrent | Mostly concurrent |
| Object relocation | Mostly concurrent | Primarily during STW evacuation |
| Compaction | Concurrent | Incremental STW evacuation |
| Typical pause behavior | Sub-millisecond target | Milliseconds to hundreds of milliseconds |
| Throughput cost | Can be higher | Often lower |
| Barrier complexity | Higher | Lower |
| Best fit | Strict latency requirements | General enterprise workloads |
| Heap size | Small to multi-terabyte | Medium to large heaps |
| Tuning focus | Heap headroom and CPU | Pause target and heap behavior |
ZGC is not automatically better than G1.
Choose based on actual:
- Latency targets
- Throughput targets
- CPU availability
- Memory capacity
- Allocation rate
- Production measurements
OpenJDK characterizes ZGC as latency-oriented, Parallel GC as throughput-oriented, and G1 as balancing latency and throughput.
ZGC vs Parallel GC
| Feature | ZGC | Parallel GC |
|---|---|---|
| Goal | Low latency | Maximum throughput |
| GC execution | Mostly concurrent | Stop-The-World |
| Pause duration | Very short | Can be long |
| CPU usage | Concurrent with application | Intensive during pauses |
| Heap compaction | Concurrent | Stop-The-World |
| Best use case | Interactive services | Offline batch jobs |
For a nightly batch process where pauses do not matter, Parallel GC may provide better total throughput.
For a customer-facing payment API with strict tail-latency requirements, ZGC may be more appropriate.
Does ZGC Guarantee Sub-Millisecond Pauses?
No Garbage Collector can guarantee a specific pause under every possible environment.
Pause behavior can be affected by:
- Operating-system scheduling
- CPU throttling
- Virtual-machine contention
- Safepoint synchronization
- Large root sets
- JNI critical sections
- Host overload
- Monitoring agents
- Container limits
ZGC is designed to keep GC pause times extremely short, but production validation is still necessary.
ZGC and Throughput
Low latency is not free.
ZGC performs Garbage Collection concurrently with application threads, meaning GC and business logic share CPU resources.
Possible costs include:
- Barrier overhead
- Concurrent GC CPU usage
- Additional memory headroom
- Lower peak throughput than Parallel GC
- Increased infrastructure cost
The trade-off is much lower pause latency.
ZGC and CPU Resources
Because ZGC performs concurrent work, sufficient CPU is important.
If a container has:
resources:
requests:
cpu: "1"
limits:
cpu: "1"
the application and ZGC must share one CPU.
This can cause:
- Slower concurrent marking
- Slower relocation
- Allocation stalls
- Reduced throughput
- Higher request latency
ZGC generally benefits from multiple available processors.
ZGC in Containers and Kubernetes
Container memory includes more than Java heap.
Container Memory
|
+-- Java Heap
+-- Metaspace
+-- Thread Stacks
+-- Direct Buffers
+-- Code Cache
+-- Native Libraries
+-- ZGC Metadata
+-- JVM Internal Memory
Bad configuration:
memory limit: 8Gi
-Xmx8g
This leaves little or no space for native and non-heap memory.
A safer starting configuration could be:
-Xms4g
-Xmx6g
for an 8 GiB container, but the correct value must be based on measurements.
Example Spring Boot Configuration
For JDK 21 Generational ZGC:
java \
-Xms4g \
-Xmx12g \
-XX:+UseZGC \
-XX:+ZGenerational \
-XX:SoftMaxHeapSize=10g \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/dumps/application.hprof \
-Xlog:gc*:file=/logs/zgc.log:time,uptime,level,tags:filecount=10,filesize=20M \
-jar payment-service.jar
For JDK 24 and later:
java \
-Xms4g \
-Xmx12g \
-XX:+UseZGC \
-XX:SoftMaxHeapSize=10g \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/dumps/application.hprof \
-Xlog:gc*:file=/logs/zgc.log:time,uptime,level,tags:filecount=10,filesize=20M \
-jar payment-service.jar
ZGC Logging
Basic logging:
-Xlog:gc
Detailed logging:
-Xlog:gc*
Debug logging:
-Xlog:gc*=debug
Write logs to a file:
-Xlog:gc*:file=/logs/zgc.log:time,uptime,level,tags
Enable rotation:
-Xlog:gc*:file=/logs/zgc.log:time,uptime,level,tags:filecount=10,filesize=20M
Simplified ZGC Log Example
GC(42) Garbage Collection (Warmup)
GC(42) Pause Mark Start 0.021ms
GC(42) Concurrent Mark 12.345ms
GC(42) Pause Mark End 0.018ms
GC(42) Concurrent Select Relocation Set 0.451ms
GC(42) Concurrent Relocate 4.781ms
GC(42) Garbage Collection 8192M(65%)->5632M(45%)
Interpretation:
- Collection number:
GC(42) - Reason:
Warmup - Mark Start pause: very short
- Concurrent Mark: application continues running
- Mark End pause: very short
- Relocation-set selection: concurrent
- Relocation: concurrent
- Heap usage dropped from approximately 8 GB to 5.5 GB
Exact log wording depends on the JDK version.
Important ZGC Metrics
Monitor:
- Pause Mark Start
- Pause Mark End
- Concurrent Mark duration
- Concurrent Relocate duration
- Allocation rate
- Live-set size
- Heap usage after collection
- Young collection frequency
- Major collection frequency
- Promotion rate
- Allocation stalls
- GC CPU usage
- Concurrent GC worker utilization
- Large-object allocation
- Container memory
- Native memory
- Safepoint duration
- Request p95, p99, and p99.9 latency
Frequently Asked Interview Questions
Question 1: What is ZGC?
Answer
ZGC is a scalable, low-latency, compacting Garbage Collector that performs most marking and relocation work concurrently with application threads.
Its primary purpose is to minimize Garbage Collection pause times.
Question 2: Why is ZGC considered a low-latency collector?
Answer
ZGC moves expensive operations such as:
- Marking
- Reference processing
- Relocation
- Compaction
out of long Stop-The-World pauses and performs them concurrently.
Only short coordination phases stop application threads.
Question 3: Is ZGC completely pause-free?
Answer
No.
ZGC still has short Stop-The-World phases, including root-processing and collection-transition work.
The important difference is that these pauses do not perform most heap-wide heavy operations.
Question 4: Does ZGC pause time increase with heap size?
Answer
ZGC is designed so pause duration is largely independent of heap size and live-set size because most heap processing is concurrent.
However, real-world pauses can still be influenced by root-set size, operating-system scheduling, safepoint delays, CPU contention, and native code.
Question 5: What are colored pointers?
Answer
Colored pointers are object references that also encode Garbage Collection state information.
ZGC uses this metadata to determine whether a reference requires marking, remapping, or relocation-related processing.
Question 6: What is a load barrier?
Answer
A load barrier is JVM-generated logic that runs when an object reference is loaded.
It verifies that the reference points to the object's current valid location and repairs stale references when necessary.
Question 7: What is a store barrier?
Answer
A store barrier runs when an object reference is written.
Generational ZGC uses store barriers to track reference relationships, especially old-to-young references and changes relevant to concurrent marking.
Question 8: What is self-healing in ZGC?
Answer
When a thread loads a stale reference, the barrier locates the object's new address and repairs the reference.
Future accesses can then use the corrected reference directly.
Question 9: What is concurrent relocation?
Answer
Concurrent relocation moves live objects from selected heap pages while application threads continue running.
Barriers ensure that threads accessing relocated objects find the correct current address.
Question 10: How does ZGC compact the heap?
Answer
ZGC selects pages with reclaimable space, moves their live objects into other pages, and then returns the original pages to the free-memory pool.
Because relocation is mostly concurrent, compaction does not require a long global pause.
Question 11: What is a relocation set?
Answer
A relocation set is the collection of heap pages selected for object relocation during a ZGC cycle.
Live objects are moved out, and the selected pages are reclaimed.
Question 12: What is a forwarding table?
Answer
A forwarding table maps an object's previous address to its new location during relocation.
Load barriers use this information to resolve and repair stale references.
Question 13: Is ZGC generational?
Answer
Modern ZGC is generational.
Generational ZGC was introduced in JDK 21, became the default ZGC mode in JDK 23, and the older non-generational mode was removed in JDK 24.
Question 14: Why was Generational ZGC introduced?
Answer
Most objects die young.
Separating young and old objects allows ZGC to collect recently created objects frequently without repeatedly processing the complete old object set.
This reduces GC work and improves throughput.
Question 15: What is a minor collection in Generational ZGC?
Answer
A minor collection primarily processes the young generation.
It reclaims short-lived objects and promotes surviving objects when appropriate.
Question 16: What is a major collection?
Answer
A major collection performs broader collection work involving both young and old generations.
It includes old-generation marking and reclamation and occurs less frequently than young collections.
Question 17: What is the most important ZGC tuning option?
Answer
The most important option is maximum heap size:
-Xmx
ZGC requires sufficient heap headroom so application allocations can continue while collection runs concurrently.
Question 18: Why does ZGC need additional heap headroom?
Answer
The application continues allocating objects while ZGC is marking and relocating existing objects.
The heap must therefore contain enough free space for:
- New allocations
- Surviving objects
- Relocation
- Temporary spikes
Without adequate headroom, application threads can stall.
Question 19: What is an allocation stall?
Answer
An allocation stall occurs when an application thread requests heap memory but ZGC has not yet reclaimed enough free space.
The thread must wait for collection progress.
Question 20: Can ZGC still throw OutOfMemoryError?
Answer
Yes.
ZGC cannot solve:
- Memory leaks
- An undersized heap
- Excessive live data
- Unbounded caches
- Large allocation spikes
- Native-memory exhaustion
If insufficient memory can be reclaimed, the JVM can still throw OutOfMemoryError.
Question 21: Does ZGC prevent memory leaks?
Answer
No.
The collector can reclaim only unreachable objects.
Objects retained by static collections, caches, queues, listeners, or ThreadLocal values remain reachable and cannot be collected.
Question 22: What is SoftMaxHeapSize?
Answer
SoftMaxHeapSize provides a soft heap-usage goal below the hard Xmx limit.
For example:
-Xmx16g
-XX:SoftMaxHeapSize=12g
ZGC attempts to remain near 12 GB during normal conditions but may use up to 16 GB when necessary.
Question 23: Can ZGC return memory to the operating system?
Answer
Yes.
ZGC can uncommit unused heap memory when the current live set and minimum heap configuration allow it.
Question 24: Is ZGC always faster than G1GC?
Answer
No.
ZGC usually provides much shorter pauses, but it may consume:
- More concurrent CPU
- More heap headroom
- Additional barrier processing
G1 may provide better throughput for applications that do not require extremely low pause times.
Question 25: When should ZGC be preferred over G1GC?
Answer
Choose ZGC when:
- Tail latency is critical.
- Long GC pauses are unacceptable.
- The application has sufficient CPU resources.
- Adequate heap headroom is available.
- Large heaps are used.
- Production testing shows that G1 pause times violate service-level objectives.
Question 26: When should you avoid ZGC?
Answer
ZGC may not be the best choice when:
- Maximum batch throughput is the only goal.
- CPU resources are extremely limited.
- Memory headroom is very small.
- The application uses a tiny heap.
- G1 already meets latency requirements.
- Collector complexity provides no measurable benefit.
Question 27: Is ZGC suitable for Spring Boot microservices?
Answer
Yes, but it should not be selected automatically for every microservice.
It is useful for services with:
- Strict latency objectives
- Large heaps
- Large in-memory state
- High request volume
- Costly G1 pause spikes
For a small service with a 512 MB heap, G1 may already be sufficient.
Question 28: How does CPU throttling affect ZGC?
Answer
ZGC relies on concurrent worker threads.
CPU throttling can slow marking and relocation, allowing allocations to consume available headroom before GC completes.
This can lead to allocation stalls and poor throughput.
Question 29: What should you investigate when ZGC pauses are low but latency is still high?
Answer
Check:
- Allocation stalls
- CPU throttling
- Safepoint delays
- Thread-pool exhaustion
- Database latency
- Network calls
- Lock contention
- Direct-memory pressure
- Host-level resource contention
Low GC pauses do not prove the application is healthy.
Question 30: What are the key ZGC production indicators?
Answer
The most important indicators include:
- Allocation stall count and duration
- Heap occupancy after collection
- Live-set growth
- Allocation rate
- Young and major collection frequency
- GC CPU overhead
- Container CPU throttling
- Native-memory usage
- Application p99 latency
Real-Time Production Scenario 1: Allocation Stalls
Problem
A payment API uses ZGC with:
-Xmx8g
Monitoring shows:
- GC pauses below one millisecond
- Request latency occasionally exceeds five seconds
- Allocation stalls appear in GC logs
- Heap stays above 90%
Root cause
The collector pauses are short, but ZGC does not have enough heap headroom to complete concurrent work before the application consumes available memory.
Resolution
Investigate:
- Live object set
- Allocation rate
- Cache size
- Container memory limit
- CPU throttling
- Large temporary objects
Possible solutions:
- Increase heap headroom.
- Reduce retained objects.
- Bound caches.
- Reduce allocation rate.
- Add CPU capacity.
- Stream large payloads.
- Fix memory leaks.
The solution is not to optimize pause time because the pauses are already short.
Real-Time Production Scenario 2: CPU Throttling
Pod resources
resources:
requests:
cpu: "500m"
memory: "8Gi"
limits:
cpu: "1"
memory: "8Gi"
JVM configuration:
-Xmx6g
-XX:+UseZGC
Symptoms
- Concurrent marking takes longer during traffic spikes.
- Allocation stalls increase.
- Application throughput decreases.
- GC pauses remain low.
Root cause
The application and ZGC share a severely limited CPU allocation.
ZGC cannot complete concurrent work quickly enough.
Potential resolution
- Increase CPU request and limit.
- Reduce application allocation rate.
- Reduce unnecessary object creation.
- Validate whether G1 provides better throughput under the available CPU constraint.
- Measure collector CPU consumption with Java Flight Recorder.
Real-Time Production Scenario 3: Large File Processing
Bad implementation
public void process(MultipartFile file) throws IOException {
byte[] data = file.getBytes();
parseCompleteFile(data);
}
A 1 GB file produces a very large array.
Problems
- Immediate heap pressure
- Reduced ZGC headroom
- Possible allocation stall
- Multiple temporary copies
- Possible OutOfMemoryError
Better implementation
public void process(MultipartFile file) throws IOException {
try (InputStream input = file.getInputStream();
BufferedInputStream buffered = new BufferedInputStream(input)) {
byte[] buffer = new byte[64 * 1024];
int count;
while ((count = buffered.read(buffer)) != -1) {
processChunk(buffer, count);
}
}
}
Streaming protects heap headroom and reduces allocation pressure.
Real-Time Production Scenario 4: Memory Leak
Symptoms
- Post-GC heap usage continuously increases.
- Major collections become more frequent.
- ZGC returns progressively less memory.
- Allocation stalls eventually appear.
- Application ends with
OutOfMemoryError.
Root cause
An unbounded static cache:
private static final Map<String, CustomerSession> CACHE =
new ConcurrentHashMap<>();
Sessions are inserted but never removed.
Explanation
ZGC correctly treats cached sessions as live because they remain reachable from a static GC root.
No collector can reclaim reachable objects.
Resolution
- Use a bounded cache.
- Add expiration.
- Remove completed sessions.
- Capture a heap dump.
- Analyze dominator trees and retained sizes.
Real-Time Production Scenario 5: Migrating from G1 to ZGC
Existing system
A Spring Boot API uses G1:
-Xmx16g
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
Production metrics show:
- Median latency: 40 milliseconds
- p99 latency: 900 milliseconds
- Mixed GC pauses: 500–800 milliseconds
- Throughput is otherwise acceptable
Migration test
-Xmx16g
-XX:+UseZGC
What should be compared?
- p50 latency
- p95 latency
- p99 latency
- p99.9 latency
- Requests per second
- CPU utilization
- Heap usage
- Allocation stalls
- GC CPU time
- Infrastructure cost
- Application startup time
Do not migrate based only on average GC pause time.
The collector should improve the application's actual service-level objectives.
ZGC Troubleshooting Flow
flowchart TD
Issue["Latency or Memory Issue"] --> Logs["Analyze ZGC Logs"]
Logs --> Stall{"Allocation Stalls?"}
Stall -->|Yes| Headroom["Check Heap Headroom"]
Headroom --> Live["Measure Live Set"]
Headroom --> Rate["Measure Allocation Rate"]
Headroom --> CPU["Check CPU Throttling"]
Stall -->|No| Heap{"Post-GC Heap Increasing?"}
Heap -->|Yes| Leak["Capture Heap Dump"]
Leak --> Retained["Analyze Retained Objects"]
Heap -->|No| Latency["Investigate Non-GC Latency"]
Latency --> Locks["Locks and Thread Pools"]
Latency --> IO["Database and Network"]
Latency --> Safepoints["Safepoint Delays"]
Best Practices
- Use a recent supported JDK for the latest ZGC improvements.
- Prefer Generational ZGC in supported modern JDKs.
- Size
Xmxwith sufficient concurrent-allocation headroom. - Monitor allocation stalls, not only pause times.
- Leave room for native and non-heap memory.
- Avoid setting
Xmxequal to the container memory limit. - Stream large files and payloads.
- Use bounded caches.
- Provide sufficient CPU for concurrent GC workers.
- Monitor p99 and p99.9 application latency.
- Enable GC log rotation.
- Capture heap dumps on
OutOfMemoryError. - Compare ZGC against G1 under production-like load.
- Fix application leaks before tuning the collector.
- Change one JVM setting at a time.
Common Mistakes
❌ Assuming ZGC has zero pauses.
❌ Choosing ZGC only because it is newer.
❌ Monitoring pause time while ignoring allocation stalls.
❌ Configuring an undersized heap.
❌ Setting heap size equal to the container limit.
❌ Ignoring concurrent GC CPU consumption.
❌ Loading entire large files into byte arrays.
❌ Expecting ZGC to fix reachable-object memory leaks.
❌ Comparing collectors using only average latency.
❌ Using outdated non-generational ZGC flags on newer JDKs.
❌ Assuming low GC pauses guarantee low application latency.
Senior Interview Tips
Senior interviewers may ask you to explain:
- Why ZGC pause times remain small
- How concurrent relocation works
- Why load barriers are required
- How stale references are repaired
- What colored pointers represent
- Why Generational ZGC improves throughput
- Difference between minor and major ZGC cycles
- Why heap headroom matters
- What causes allocation stalls
- Why CPU throttling affects ZGC
- How ZGC performs concurrent compaction
- How to diagnose low GC pauses with poor application latency
- When G1GC is preferable to ZGC
- How to configure ZGC in Kubernetes
- How JDK 21, JDK 23, and JDK 24 ZGC modes differ
A strong answer should connect collector internals to production symptoms.
Quick Revision Table
| Concept | Interview answer |
|---|---|
| ZGC | Concurrent, compacting, low-latency collector |
| Colored pointer | Object reference containing GC-state metadata |
| Load barrier | Validates and repairs loaded references |
| Store barrier | Tracks reference updates and generation relationships |
| Self-healing | Repairs a stale reference during application access |
| Relocation Set | Heap pages selected for evacuation |
| Forwarding Table | Maps old object addresses to new addresses |
| Concurrent Mark | Finds reachable objects while application threads run |
| Concurrent Relocation | Moves live objects without a long global pause |
| Generational ZGC | Separates young and old objects |
| Minor Collection | Primarily collects the young generation |
| Major Collection | Broader collection involving old-generation work |
| Heap Headroom | Free memory required while concurrent GC runs |
| Allocation Stall | Application waits because free heap is unavailable |
| SoftMaxHeapSize | Soft usage goal below the hard Xmx limit |
Summary
In this article, we covered:
- ZGC architecture
- Low-latency Garbage Collection
- Concurrent marking
- Concurrent relocation
- Concurrent compaction
- Colored pointers
- Load barriers
- Store barriers
- Self-healing references
- Relocation sets
- Forwarding tables
- Generational ZGC
- Young and old generations
- Minor and major collections
- Heap sizing
- Heap headroom
- Allocation stalls
- Soft maximum heap size
- Containers and Kubernetes
- GC logging
- Production troubleshooting
- G1GC versus ZGC
- Frequently asked interview questions
- Best practices
- Common mistakes
In the next article, we will explore Shenandoah GC, including:
- Concurrent marking
- Concurrent evacuation
- Concurrent reference updating
- Brooks forwarding pointers
- Load-reference barriers
- SATB
- Generational Shenandoah
- Shenandoah versus ZGC
- Production tuning and troubleshooting