1. Welcome Developer Playground by Giri

JVM Performance

About ZGC

Updated: March 31, 2025

Latest Updates in JDK 24

JDK 24 was officially released in March 2024, bringing several improvements to ZGC. These enhancements continue to refine ZGC's performance characteristics and operational efficiency.

Generational ZGC Improvements

The generational implementation of ZGC, first introduced in JDK 21, has been significantly stabilized in JDK 24. Key improvements include:
  • Optimized Inter-Generational Reference Processing - The algorithms for handling references between generations have been refined, improving efficiency when objects in the old generation reference objects in the young generation.
  • Enhanced Remembered Set Management - Improved handling of the remembered set (the data structure tracking old-to-young references) reduces overhead and improves scalability in large applications.
  • More Adaptive Generation Sizing - The proportions of heap allocated to young and old generations are now more dynamically adjusted based on application behavior patterns.

Memory Management Enhancements

JDK 24 introduces several improvements to ZGC's memory management capabilities:
  • Improved Memory Return Mechanism - The process of returning unused memory to the operating system has been optimized to be less intrusive to application performance, particularly valuable in container environments where resource efficiency is critical.
  • Large Heap Optimization - For very large heaps (8TB+), memory mapping efficiency has been improved, enabling better performance in memory-intensive enterprise applications.
  • Reduced Memory Fragmentation - Enhanced compaction algorithms minimize fragmentation more effectively, improving memory utilization in long-running applications.

Diagnostic Improvements

JDK 24 enhances ZGC's observability and diagnostic capabilities:
  • Enhanced GC Logging - More detailed and structured logging helps identify potential issues and optimization opportunities. New log categories provide insight into specific ZGC operations.
  • Extended JFR Events - Java Flight Recorder now includes additional ZGC-specific events, allowing for more granular performance analysis of garbage collection activities.
  • Improved JMX Metrics - New management metrics have been added for monitoring ZGC's operation through JMX, aiding in real-time monitoring solutions.

Configuration and Tuning

JDK 24 continues the trend of making ZGC more self-tuning:
  • Further Reduced Need for Manual Thread Sizing - The automatic thread management has been improved, making -XX:ConcGCThreads even less necessary in most deployments.
  • More Efficient Default Behavior - Out-of-the-box performance is improved, with better default settings for most common workloads.

Future Direction

Looking ahead to JDK 25 and beyond, the ZGC development team is focusing on several areas:
  • Further Parallel Processing Optimization - Enhancing the scalability of ZGC on many-core systems, allowing efficient utilization of increasingly parallel hardware.
  • Refined Generational Strategies - More sophisticated object aging and promotion policies to better distinguish between short-lived and long-lived objects.
  • Tighter Container Integration - Improved awareness and adaptation to containerized environments, particularly in cloud-native deployments.

JDK 24 Upgrade Recommendation

If you're using ZGC in production environments, upgrading to JDK 24 is recommended, especially for applications that benefit from generational garbage collection. The stability and performance improvements provide meaningful benefits with minimal migration effort.

Z Garbage Collector (ZGC) ArchitectureZGC Memory LayoutZ HeapZPage (2MB / 4MB Medium)ZPage (2MB Medium)ZPage (Small)ZPage (Small)ZPage (32MB Large)ZPage (N*2MB Huge)Colored Pointers42 bits: Object Address4 bits: Color (Metadata)ZGC PhasesPause: Mark Start(Short Pause - Load Barriers Activated)Concurrent Mark(Application Threads Continue)Pause: Mark End(Short Pause - Process References)Concurrent Process Non-Strong References(Weak, Soft, Phantom, JNI References)Concurrent Reset & Remap(Reset Metadata, Remap Physical Memory)Concurrent Compaction(Relocate Objects & Update References)ZGC FeaturesPause Phases (STW): <10ms regardless of heap sizeConcurrent Phases: Application continues while GC worksLoad Barriers with Colored PointersRegion-based Memory ManagementGenerational ZGC (JDK 21+)Young GenerationOld Generation

Z Garbage Collector

Z Garbage Collector was introduced as an experimental feature in JDK 11 and was officially released in JDK 15. It is also available in JDK 21.
ZGC performs high-cost operations concurrently without stopping application threads for more than 10ms. The pause time is independent of heap size, working effectively with heaps ranging from a few hundred megabytes to 16 terabytes.

For JDK 11 to JDK 15, ZGC is activated by using both the -XX:+UnlockExperimentalVMOptions and -XX:+UseZGC options. For JDK 15 and later, only the -XX:+UseZGC option is needed.

Like G1 GC, ZGC is a concurrent garbage collector that uses the following key features:

  • Concurrent
  • Region-based
  • Compacting
  • NUMA-aware
  • Using colored pointers
  • Using load barriers
  • Using store barriers (in the generational mode)
Generational ZGC (JDK 21+)Generational ZGC Memory LayoutYoung GenerationEden SpaceSurvivor 1Survivor 2Old GenerationLong-lived ObjectsPromotionBoth generations are composed of ZPages (2MB, 4MB, 32MB, etc.)Generational Collection CycleMinor Collection (Young Gen)1. Young generation fills up2. Collect garbage in young generation3. Promote survivors to old generationFrequent, QuickStore Barriers track referencesfrom old to young generationMajor Collection (Full Heap)1. Old generation fills up2. Collect garbage across entire heap3. Compact memory if neededLess Frequent, More ThoroughLoad Barriers ensure correctnessduring concurrent relocationTriggersWhen old gen fillsKey Benefits of Generational ZGCBetter GC EfficiencyLower LatencyImproved Throughput

Configuration & Tuning

Like G1 GC, ZGC requires minimal configuration and adapts automatically during application execution. ZGC dynamically resizes generations, adjusts the number of GC threads, and modifies tenuring thresholds. The primary tuning point is increasing the maximum heap size (-Xmx).
ZGC has two versions: generational and non-generational. The non-generational version is legacy and doesn't use the generation concept during execution. The generational version was released with JDK 21 and is recommended for use.
The generational version uses both -XX:+UseZGC and -XX:+ZGenerational options.
The non-generational version uses only the -XX:+UseZGC option.

Setting Heap Size

Headroom

JVM Heap Headroom refers to the free space between the memory allocated for storing actively used data and the maximum heap size (-Xmx). This buffer allows the system to create more objects or process more data while helping reduce GC latency.
Without headroom, Out of Memory (OOM) errors can occur, but having too much free space wastes system resources. Monitoring and managing JVM Heap Headroom is essential for performance optimization and stability, preventing memory-related issues while ensuring efficient execution.
The most important tuning option for ZGC is setting the maximum heap size with the -Xmx option. Since ZGC is a concurrent collector, you must choose a heap size that provides sufficient headroom for memory allocation while the service operates during GC activity and can accommodate the application's live-set. The headroom size depends on the application's allocation rate and live-set size. Generally, providing more heap memory to ZGC is beneficial, but excessive heap sizes are not recommended, so finding the right balance is important.

Another heap size-related option in ZGC is -XX:SoftMaxHeapSize, which sets a soft limit on how large the heap can grow. ZGC tries not to exceed this limit but may grow up to the maximum heap size if necessary. This exceeds the soft limit only when needed to prevent the application from pausing while waiting for GC to reclaim memory. For example, with -Xmx5g -XX:SoftMaxHeapSize=4g, ZGC typically uses up to 4GB but can temporarily use up to 5GB.

Setting Concurrent GC Threads

When using the non-generational version, concurrent GC threads can be tuned with -XX:ConcGCThreads=<number>. ZGC has heuristics to automatically set the thread count, which generally works well but may need adjustment based on application characteristics. This option fundamentally determines how much CPU time to allocate to GC. Setting too many threads can cause GC to take excessive CPU time from the application, leading to performance degradation. Setting too few can cause the application to allocate garbage faster than GC can collect it.
From JDK 17 onward, ZGC dynamically expands and shrinks the number of concurrent GC threads, eliminating the need to configure this option.
With the release of the generational version in JDK 21, the need to set concurrent GC threads has been further reduced.
If your application requires minimized GC delays and fast response times, ensure CPU utilization does not exceed 70%.

Returning Unused Memory to the Operating System

By default, ZGC uncommits unused memory and returns it to the OS. This can negatively impact application thread latency. You can disable this feature with the -XX:-ZUncommit option. Additionally, memory is not uncommitted below the minimum heap size (-Xms). Setting -Xmx and -Xms to the same value implicitly disables this default behavior.
You can configure the uncommit delay with -XX:ZUncommitDelay=<seconds> (default 300 seconds). This specifies how long memory must remain unused before becoming eligible for uncommitting. By default, memory unused for 300 seconds (5 minutes) is uncommitted and returned to the OS.
When an application is running, memory committing or uncommitting by GC can negatively impact application thread latency. For faster response times, set -Xmx and -Xms to the same value. Also, use the -XX:+AlwaysPreTouch option (supported from JDK 14) to move memory to pages. However, this forces all allocated heap memory pages to be pre-mapped to physical memory when the JVM starts, which can cause problems in container-based cloud environments if it uses more memory than allocated to the container.
On Linux, uncommitting unused memory requires the fallocate(2) function that supports FALLOC_FL_PUNCH_HOLE, first introduced in kernel 3.5 (for tmpfs) and 4.3 (for hugetlbfs).

Using Large Pages

Enabling large pages typically provides better throughput, latency, and startup time. However, configuration is complex and requires root privileges, so it's disabled by default. Setup instructions are available at https://wiki.openjdk.org/display/zgc/Main#Main-JDK17.

Enabling NUMA (Non-Uniform Memory Access) Support

By default, NUMA support is enabled, but it can be automatically disabled if the JVM detects that memory can only be used from a single NUMA node. This setting typically doesn't require attention. However, if you want to explicitly override the JVM's decision, you can manually enable or disable it using -XX:+UseNUMA or -XX:-UseNUMA. When running on NUMA machines (e.g., a multi-socket x86 machine), having this option enabled can provide noticeable performance improvements.

References

Conclusion

ZGC represents a significant advancement in Java garbage collection technology, focusing on low-latency while still maintaining good throughput. It's particularly well-suited for applications that:
  • Need consistent performance with minimal pauses
  • Work with large heaps (multi-GB to TB scale)
  • Have strict response time requirements
  • Run on modern multi-core systems
With the introduction of the generational version in JDK 21, ZGC continues to evolve, becoming more efficient and requiring less manual tuning. This follows the general trend in JVM development toward more adaptive, self-tuning components that minimize the need for developers to become garbage collection experts.
When migrating to ZGC from other collectors like G1 or Parallel GC, start with the default settings and monitor your application's performance metrics. In most cases, minimal tuning will be required beyond setting appropriate heap size limits. For latency-sensitive applications, remember to consider both maximum heap size and the relationships between minimum and maximum values to control memory uncommitting behavior.
If you're running Java applications with high-throughput requirements that also need predictable, low pause times, ZGC should be among your top considerations for garbage collection strategies in modern JVM deployments.
Copyright © 2025 Giri Labs Inc.·Trademark Policy