1. Introduction
In this tutorial, we’ll discuss the Z garbage collector(ZGC) in Java.
ZGC is a scalable low-latency garbage collector. In Java 11, ZGC was introduced as an experimental feature. In Java 15, this was included as a product feature in Java after receiving positive feedback and fixing of bugs.
ZGC was integrated into JDK 11 by JEP 333. ZGC supports all commonly used platforms:
- Linux/x86_64 (JEP 333)
- Linux/aarch64 (8214527)
- Windows (JEP 365)
- macOS (JEP 364)
Some of the important highlights of ZGC are:
- Concurrent class unloading
- Uncommitting unused memory (JEP 351)
- 16TB of maximum heap size
- 8MB of minimum heap size
- Support for placing the heap on NVRAM
- Limited and discontiguous address spaces
- Support for the JFR leak profiler
- Support for class-data sharing
- Improved NUMA awareness
ZGC performs all expensive tasks concurrently, without stopping the execution of application threads for more than a few milliseconds. It is suitable for applications which require low latency. ZGC pause times are independent of heap size that is being used. ZGC performs every GC process concurrently, that is why the pause phases are short and not increase with the increase in live-set size. This gives consistent performance for the application.
When initially using ZGC, it’s advisable to enable GC logging. This provides additional diagnostic information about ZGC’s activities, which can be valuable for tuning and comparing performance improvements or regressions against other garbage collectors. You can enable GC logging at the info level with the following setting:
-Xlog:gc
ZGC is a good choice for web applications where large number of requests are served and large live-sets are maintained.
2. How to enable ZGC?
In Java 11, the ZGC is enabled by using
-XX:+UnlockExperimentalVMOptions -XX:+UseZGC
In Java 15, the -XX:+UnlockExperimentalVMOptions
is not needed.
3. Tuning the Z garbage collector
ZGC was designed to require minimal configuration. In most cases, the only configuration needed is to set max heap size -Xmx
. However, some tuning could be required based on needs.
- Heap size: The most important tuning option for ZGC is setting the max heap size (
-Xmx
). ZGC is a concurrent collector. The heap size must be sufficient to accommodate the live-set of the application and still sufficient space left to allow allocations to be serviced while the GC is running. - Number of Concurrent GC Threads: The number of concurrent GC threads can be set using
-XX:ConcGCThreads
. This option defines how much CPU-time should be given to the GC. - Returning Unused Memory to the Operating System: By default, ZGC uncommits unused memory, returning it to the operating system. This is useful for applications where memory footprint is very important. This feature can be disabled using
-XX:-ZUncommit
. Memory will not be uncommitted so that the heap size shrinks below the minimum heap size. You can configure an uncommit delay with the option-XX=<seconds>
(default is 300 seconds). This setting determines the period that memory must remain unused before it becomes eligible for uncommit.
4. When to Use ZGC and When to Avoid ZGC
GZC was designed for particular requirements: low latency and large live sets. ZGC was not designed to replace G1 GC, Parallel GC, or Serial GC. ZGC is not recommended to use in cases where the heap size is small as 8 MB. with single-core processor.
5. ZGC GC Cycle
The GC cycle consists of three pauses and three concurrent phases.
- Three pauses: Pause Mark Start, Pause Mark End, Pause Relocate Start
- Three concurrent phases: Concurrent Mark, Concurrent Prepare for Relocation, Concurrent Relocate
Following diagram shows a simplified view of ZGC’s GC cycle.
- Pause Mark Start: This signals the start of mark phase. During this and all subsequent pause phases, only minor actions are performed, such as setting boolean flags.
- Concurrent Mark: During this concurrent phase, ZGC will walk the entire object graph and mark all objects.
- Pause Mark End: Synchronization point to signal the end of marking.
- Concurrent Prepare for Relocation: During this concurrent phase, ZGC will remove objects.
- Pause Relocate Start: Synchronization point signals to threads that objects will be moved around in the heap.
- Concurrent Relocate: During this concurrent phase, ZGC will move objects and compact regions in the heap to free up space.
6. Colored Pointers
A very important aspect of garbage collection (GC) is the relocation of objects within the heap while ensuring that the application does not use outdated references to these moved objects. A straightforward approach to guarantee this is to pause the application during such operations. However, ZGC aims to minimize pause times, requiring nearly all its tasks to be executed concurrently. This poses a potential challenge: Even though ZGC carries out its tasks while the application is running, it must guarantee the application always accesses the correct reference. ZGC achieves this through two key architectural strategies: colored pointers and load barriers. As of JDK 19, colored pointers in ZGC are depicted as follows:
Currently, 4 bits are utilized, whereas the remaining 18 bits are reserved for potential future use. The purpose of each bit is as follows:
- Finalizable: This bit signifies whether the object is accessible solely through a finalizer. It’s important to note that finalization was marked as deprecated for removal in JDK 18 as part of JEP 421.
- Remapped: This bit shows that the pointer is confirmed not to point into the relocation set.
- Marked0 & Marked1: These bits indicate whether the object has been marked by the garbage collector. ZGC alternates between these two bits, designating one as “active” for each GC cycle.
Each bit has a “good” and “bad” designation, but what qualifies as “good” or “bad” depends on the specific context in which the object is accessed. The application is unaware of the colored pointers; the interpretation of these pointers is managed by load barriers when an object is fetched from heap memory.
7. Heap multi-mapping
Since ZGC can relocate an object’s physical position in the heap while the application is still running, there must be multiple pathways to the object’s current physical location. ZGC achieves this through heap multi-mapping. This method involves mapping the object’s physical location to three separate views in virtual memory, each representing a different possible “color” of the pointer. This setup allows the load barrier to find the object even if it has been moved since the last synchronization point.
8. Load Barriers
Load barriers are code segments inserted by the C2 compiler, which is part of the Just-In-Time (JIT) compilation process, into class files as the JVM processes them. These load barriers are placed in class files at points where an object is retrieved from the heap. The following Java code example from documentation illustrates where a load barrier would be inserted:
Object o = obj.fieldA(); <load barrier added here by C2> Object p = o; //No barrier, not a load from the heap o.doSomething(); //No barrier, not a load from the heap int i = obj.fieldB(); //No barrier, not and object reference
The load barrier introduces functionality that examines the “colors” of an object’s pointer when it is retrieved from the heap. Load barriers are optimized for the typical “good” color scenario, allowing for swift processing. If a load barrier detects a “bad” color, it will attempt to rectify the situation. This could involve updating the pointer to reflect the object’s new location in the heap or even moving the object itself before the reference is returned to the system. This correction ensures that future accesses to the object from the heap will follow the more efficient path.
9. Regions
ZGC does not treat the heap as a single bucket to toss objects into but dynamically divides the heap into separate memory regions: small, medium and large.
- Small Regions: Small regions are 2 MB in size.
- Medium Regions: The size of a medium region can vary depending on what the max heap size (-Xmx) is set to.
- Max Heap Size Medium Region Size
10. Large regions
Large regions are reserved for humongous objects. Any object that exceeds the size limit for a medium region will be allocated its own large region.
11. Compaction and Relocation
The garbage collector may eventually transfer objects from a region primarily filled with inaccessible objects to a new region, enabling deallocation of the old region and freeing up memory. This process is known as compaction and relocation. ZGC, since JDK 16, achieves compaction through two relocation methods: in-place and not-in-place.
Not-in-place relocation occurs when empty regions are accessible and is ZGC’s favored relocation approach. If there are no empty regions accessible, ZGC will resort to in-place relocation. In this scenario, ZGC relocates objects into a region with sparse occupancy.
12. Conclusion
In conclusion, the Java Z Garbage Collector (ZGC) stands as a testament to the continuous evolution of Java’s memory management systems. With its focus on minimizing pause times and maximizing throughput, ZGC introduces innovative techniques such as colored pointers, load barriers, and heap multi-mapping. By enabling concurrent garbage collection and efficient compaction strategies, ZGC provides Java developers with a powerful tool to manage memory effectively in large-scale applications. As Java continues to evolve, ZGC remains at the forefront, offering a robust solution for modern memory management challenges.