Return to site

🗑️⚡ZGC means “Z Garbage Collector,” not “Zero Garbage Collector.”

· java

First, a common misconception:

ZGC means “Z Garbage Collector,” not “Zero Garbage Collector.” (The Z is inpired by ZFS)

The “Z” does not officially stand for anything. And while ZGC dramatically reduces garbage-collection pauses, it does not eliminate them completely.

Its objective is more realistic and extremely valuable:

👉 Keep GC pauses consistently below one millisecond, independently of the heap size.

🔸 TL;DR

ZGC is a concurrent, compacting garbage collector designed for Java applications where predictable response times matter.

Most expensive garbage-collection operations happen while application threads continue running.

This allows ZGC to support heaps ranging from a few hundred megabytes to several terabytes while maintaining sub-millisecond pauses.

But ZGC is not automatically the best collector for every application. Low latency may come with additional CPU usage and a need for sufficient heap headroom.

Section image

🔸 A BRIEF HISTORY OF ZGC

▪️ JDK 11: introduced as an experimental garbage collector.

▪️ JDK 15: declared production-ready.

▪️ JDK 16: added concurrent thread-stack scanning.

▪️ JDK 21: introduced Generational ZGC.

▪️ JDK 23: Generational ZGC became the default ZGC mode.

▪️ JDK 24: the old non-generational implementation was removed.

▪️ JDK 25: brought further performance improvements, including a major overhaul of page allocation.

Over the years, ZGC evolved from an experimental low-latency collector into a production-ready option used by critical online services.

🔸 HOW ZGC KEEPS PAUSES SO SHORT

Traditional garbage collectors may perform substantial work while application threads are stopped.

ZGC moves almost all expensive operations outside those stop-the-world pauses:

▪️ discovering which objects are still alive

▪️ relocating live objects

▪️ compacting memory

▪️ reclaiming unused regions

Application threads continue executing while this work happens.

The remaining pauses are mainly short synchronization points used to ensure that application and GC threads agree on the current collection phase.

🔸 COLORED POINTERS AND BARRIERS

One of ZGC’s most distinctive mechanisms is its use of colored pointers.

Object references contain metadata that helps ZGC determine whether an object has moved and whether a reference must be updated.

Combined with load and store barriers, this allows ZGC to relocate objects concurrently without first updating every reference during a long pause.

Once an object has been copied, its former memory can be reused quickly; even while some references still point to the old location.

🔸 WHY GENERATIONAL ZGC MATTERS

Most objects created by Java applications become unreachable very quickly.

Generational ZGC takes advantage of this by separating objects into:

▪️ a young generation for newly allocated objects

▪️ an old generation for objects that survive several collections

The young generation can be collected more frequently and cheaply because relatively few objects normally survive.

This helps ZGC:

▪️ handle higher allocation rates

▪️ reduce CPU overhead

▪️ move fewer objects

▪️ avoid allocation stalls

▪️ work efficiently with smaller heaps

On JDK 25, enabling ZGC is straightforward:

java -XX:+UseZGC -Xmx16g -Xlog:gc* MyApplication

On JDK 21, Generational ZGC must be enabled explicitly:

java -XX:+UseZGC -XX:+ZGenerational -Xmx16g MyApplication

🔸 ZGC VERSUS G1

G1 remains Java’s default collector because it provides a strong balance between:

▪️ throughput

▪️ latency

▪️ memory footprint

▪️ operational simplicity

ZGC targets a more specific requirement: very short and predictable response times.

G1 may provide better throughput in some constrained environments, especially with smaller heaps. ZGC becomes particularly valuable when latency matters more than maximizing every transaction per CPU cycle.

More heap headroom also gives ZGC more time to complete concurrent collections without slowing application allocations.

🔸 WHAT CAN STILL GO WRONG?

Concurrent does not mean free.

ZGC needs CPU resources while the application is running. If the machine is already saturated, GC threads and application threads must compete for CPU time.

An allocation stall can also occur when the application creates objects faster than ZGC can reclaim memory.

That usually means you should examine:

▪️ the maximum heap size

▪️ the application’s allocation rate

▪️ the live-set size

▪️ CPU utilization

▪️ large allocations on latency-critical paths

ZGC is designed to require minimal tuning, but choosing an appropriate heap size remains important.

🔸 WHAT COMES NEXT?

One important area being explored is automatic heap sizing.

The goal is for ZGC to observe the complete environment and dynamically balance memory against CPU usage.

Instead of manually assigning a fixed heap to every JVM, ZGC could eventually consider:

▪️ other processes and containers

▪️ off-heap memory

▪️ JVM native-memory requirements

▪️ system-wide memory pressure

▪️ application response times

The long-term vision is simple:

java -XX:+UseZGC MyApplication

Enable ZGC and let the JVM manage the rest.

🔸 TAKEAWAYS

▪️ ZGC is optimized for low latency, not maximum throughput in every scenario.

▪️ It performs expensive GC operations concurrently with the application.

▪️ Its pauses are sub-millisecond and do not grow with the heap size.

▪️ Generational ZGC significantly improves scalability and allocation handling.

▪️ JDK 25 uses the generational implementation automatically.

▪️ ZGC still needs sufficient CPU capacity and heap headroom.

▪️ Test collectors using your real workload, allocation patterns and latency objectives.

There is no perfect garbage collector. There is only the collector that best matches your application’s performance contract. ☕⚡

#Java #ZGC #GarbageCollection #JVM #OpenJDK #JDK25 #JavaPerformance #LowLatency #GenerationalZGC #BackendDevelopment #SoftwareArchitecture #PerformanceEngineering

Go further with Java certification:

Java👇

Spring👇

SpringBook👇

JavaBook👇