Generational ZGC

15 minutes read

While the G1 Garbage Collector marked a major step forward from earlier collectors by breaking the heap into regions and performing much of its work concurrently, it still faced practical limits when scaling to very large heaps or demanding low-latency workloads. G1’s pause times, though shorter and more predictable than those of Serial GC or even Parallel GC, can still increase with the size of the live object set and the total heap. Certain phases require stop-the-world pauses, which can grow noticeable under heavy allocation pressure or when memory usage spikes. For many modern applications that demand consistent millisecond-level responsiveness, these pauses remain a bottleneck. As a solution to this, the Z Garbage Collector, or ZGC in short, was developed.

Evolution of ZGC

ZGC was initially released with JDK 11 as an experimental feature, and in JDK 15, it was upgraded to a production feature. Originally, the Z Garbage Collector was a non-generational garbage collector. Before the introduction of generations, ZGC managed the entire Java heap as a single logical space divided into many small regions (typically a few megabytes each). Unlike G1, these regions were not fixed as “young” or “old” — any region could hold any object. ZGC’s main goal was to keep pause times extremely low, typically below one millisecond, regardless of heap size. To achieve this, ZGC made nearly all its work concurrent with application execution.

However, as we've seen in previous topics, generational garbage collectors are simply the better alternative in most cases. The weak generational hypothesis posits that young objects tend to die young, while older objects tend to stick around. Thus, collecting young objects requires fewer resources and yields more memory, while collecting old objects requires more resources and yields less memory. We can thus improve the performance of applications that use ZGC by collecting young objects more frequently.

While non-generational ZGC achieved impressive pause-time guarantees, it treated all objects the same way. Every collection cycle had to consider the entire heap, even though most newly allocated objects in Java die young. This meant ZGC sometimes did more concurrent marking work than necessary, using extra CPU and memory bandwidth.

So, JEP 439 for Java 21 introduced Generational ZGC by extending the Z Garbage Collector to maintain separate generations for young and old objects. Generational ZGC was pushed forward as a better alternative compared to its predecessor. Non-generational ZGC, sometimes also referred to as single-generational ZGC, is the older version of ZGC, which doesn't take advantage of generations to optimize its runtime characteristics as explained by the weak generational hypothesis.

With JEP 474 for Java 23, the generational mode was made the default for ZGC. And further, with JEP 490, the non-generational mode was removed entirely. It was clear that Generational ZGC would be a better solution for most use cases. On top of this, maintaining non-generational ZGC would slow the development of other new features.

The "Z" in ZGC doesn't really stand for anything. According to the official OpenJDK wiki, ZGC is simply just a name that was inspired by ZFS, which was a revolutionary file system when it first came out. Originally, ZFS stood for Zettabyte File System, but this was abandoned later on, making ZFS just a name that didn't stand for anything.

Why ZGC

Designed from the ground up for scalability and ultra-low pause times, ZGC takes the region-based idea of G1 but removes the dependency between heap size and pause duration. It achieves this by performing nearly all GC work concurrently with application threads and by using advanced techniques such as colored pointers and load barriers to relocate and update objects without long interruptions. We will look into these techniques in detail soon. For now, you should know that ZGC is built to solve the same fundamental problem G1 was designed for, which is reducing GC pauses. But it does so through a more concurrent and modern approach that keeps applications responsive even on multi-terabyte heaps.

Here's how the official OpenJDK article defines ZGC:

The Z Garbage Collector (ZGC) is a scalable low latency garbage collector. ZGC performs all expensive work concurrently, without stopping the execution of application threads for more than a millisecond. It is suitable for applications which require low latency. Pause times are independent of the heap size that is being used. ZGC works well with heap sizes from a few hundred megabytes to 16TB. 

At its core, ZGC is designed for lower latency while maintaining higher scalability. This is achieved by reducing the pause time for threads during collection. ZGC's pause times are consistently measured in microseconds. In contrast, the pause times of the default G1 garbage collector range from milliseconds to seconds. And here is the real kicker. ZGC's low pause times are designed to be independent of heap size. Meaning workloads can use heap sizes from a few hundred megabytes all the way up to multiple terabytes while still maintaining low pause times.

Key architectural structure

At the heart of ZGC’s design are three ideas: colored pointers, load barriers, and concurrent relocation. In short, each object reference encodes a few metadata bits — its “color” — which tell the JVM about the object’s state (e.g., marked, remapped, or relocated). When an application thread loads an object reference, a load barrier checks and updates that reference if needed, ensuring it always points to the latest, valid location. This means ZGC can move objects while the program is still running, without pausing all threads.

We briefly mentioned that the pause times for ZGC are extremely low compared to other garbage collectors. While traditional GCs stop all threads to update references using STW (stop-the-world) pauses, ZGC employs a key mechanism for this called colored pointers. Each object reference created by ZGC is stored with some metadata to encode information about the object state. These encoded bits essentially color the reference, which allows ZGC to instantly know the GC state of any object reference just by inspecting its pointer. These states help determine whether the object reference is marked, remapped, finalizable, or needs to be updated. Hence, there is no need to pause the program to move objects around the heap.

Bit distribution for object reference
Source: Oracle [1].

The diagram shown above illustrates a colored pointer and how the 64-bit memory address is divided into three parts: 44 bits are used to store the object address, 4 bits are used to color the object, whereas 16 bits are left unused.

In traditional GCs, an object reference is simply a memory address pointing to where the object lives in heap memory. The reference itself contains no additional information. Current x64 CPUs don't use all of the available 64 bits to store virtual addresses, so some bits are unused. To its advantage, ZGC embeds metadata directly into the 64-bit reference itself, effectively utilizing the unused bits.

Colored pointers enable ZGC to mark object references for garbage collection concurrently. However, the application itself should not be aware of such GC details. To maintain this anonymity, load barriers are inserted in specific parts of the code by the JIT (Just In Time) compiler. Load barriers are actual segments of code that help interpret colored pointers. Let's look at a specific code example where several direct and indirect object references are made:

Name name = person.getName(); // Heap load of an object reference → ZGC load barrier is applied
<load barrier injected by JIT compiler here> // load barrier will make sure that the object reference is valid

Name aName = name; // Local variable copy → no heap load, no load barrier

name.printName(); // Uses an already-loaded reference → no additional load barrier

int age = person.getAge(); // Primitive field load → not an object reference, no load barrier

Load barriers will inspect and analyze the reference. If the pointer has bad color, meaning the reference isn't valid, the load barrier will attempt to heal the pointer by updating the pointer or relocating the object on the heap. This repair process is referred to as self-healing.

When an application thread loads an object reference, a load barrier checks and updates that reference if needed, ensuring it always points to the latest, valid location. This feature spreads the remapping work across all application threads, making it more efficient as pauses aren't required to update pointers. As objects are marked using colored pointers to reflect their GC state while the load barriers update pointers to ensure usability, ZGC can move objects while the program is still running, without employing STW pauses for a long time.

Finally, to manage all these objects, ZGC employs regionalized memory management. Essentially, the heap is divided into logical regions called pages, where groups of objects are stored. This feature allows ZGC to group together objects of similar lifespan into a single page so that large regions of memory may be reclaimed at once. ZGC also employs higher levels of dynamism by increasing or decreasing the number of active pages during runtime and allowing three different page sizes: small, medium, and large. Objects are allocated into any of the three pages based on their size:

  • Small pages: They are 2 MB in size and only objects that are less than or equal to 1/8th the size of this page (256 KB) are stored here.

  • Medium pages: The size of medium pages depends on the max heap size set for the garbage collector. Medium pages are disabled for a max heap size of less than 128 MB. For max heap sizes greater than or equal to 1 GB, 512 MB, 256 MB, and 128 MB, the page size is set to 32 MB, 16 MB, 8 MB, and 4 MB, respectively. Similar to small pages, objects that are less than or equal to 1/8th the size of the page are stored in it.

  • Large pages: Objects that are too big to fit in a medium page are stored in large pages. Only a single object is stored in a large page.

Pages are designed to take advantage of the fact that most objects created at the same time are likely to leave scope at the same time as well. However, that isn't always the case. Through internal GC heuristics, the GC might eventually copy objects out of a page populated mainly by inaccessible objects into a new page. This is done to allow the old page to be deallocated and memory to be freed up. This is called compaction and relocation, and there are two methods of doing this:

  1. When empty pages are available, ZGC performs Not-in-place relocation. This is the preferred method for ZGC:

    Pasted illustration
    Source: Oracle [1].

  2. If no empty pages are available, ZGC will use in-place relocation. In this scenario, ZGC will move objects into a sparsely populated page by first compacting the objects within the region designated for objects to be relocated into:

Pasted illustration
Source: Oracle [1].

Concurrent processing

Traditionally, garbage collectors perform most of their work during pauses to ensure that moving objects around the heap doesn't lead to application errors by using out-of-date memory references. However, as mentioned earlier, ZGC performs most of its garbage collection work concurrently while the application itself is running. ZGC utilizes the core features we previously discussed across multiple phases separated by a short pause to perform garbage collection.

The garbage collection cycle itself proceeds through concurrent phases: concurrent marking to find live objects, concurrent preparation for relocation, and concurrent relocation itself. Only brief stop-the-world pauses occur to start or finalize phases, lasting well below a millisecond. The result is a collector whose pause time does not grow with heap size, making it ideal for large-memory or latency-sensitive applications.

Concurrent phases of ZGC
Source: Oracle [1].

  1. Pause mark start: A synchronization point that signals the start of the marking phase.

  2. Concurrent mark: Objects on the heap are actively marked with colored pointers. During marking, ZGC sets and reads color bits to determine reachability.

  3. Pause mark end: Another synchronization point that signals the end of the marking phase.

  4. Concurrent preparation for relocation: ZGC actively looks for live objects in sparsely populated regions. Such objects are marked for relocation to newer regions. Empty regions are also deallocated in this phase.

  5. Pause relocate start: The final synchronization point that signals the start of relocation.

  6. Concurrent relocation: Finally, objects marked for relocation are copied to new regions, and regions from which objects were copied are then deallocated. After relocation, references are updated to point to the updated location of copied objects on the heap.

Key changes in Generational ZGC

While ZGC remains region-based and concurrent, the generational design introduces logical separation between a young generation and an old generation. The young generation consists of newly allocated objects, which are collected more frequently by the GC, whereas the old generation consists of long-lived objects, which are collected less often. Taking advantage of the weak generational hypothesis, as we previously discussed, this change makes generational ZGC superior to its predecessor in most contexts. It ensures that generational ZGC can focus on a smaller subset of the heap (young generation) more frequently, improving reclamation efficiency.

To efficiently track inter-generation references between old to young, and to perform marking responsibilities, store barriers were also introduced. A store barrier is a tiny piece of code that automatically runs whenever you write an object reference in Java (like person.address = newAddress). Its job is simple: check if an old object is now pointing to a young object, and if so, mark a small memory region as needing attention. Without store barriers, ZGC would have to scan the entire old generation (potentially terabytes) every time it collects young objects to find which young objects are still referenced.

Store barriers are tiny pieces of code that run whenever a Java program writes a reference into an object, and they are essential for making young-generation collections fast. Because generational garbage collectors clean the young generation frequently and the old generation rarely, they must know when an old object starts pointing to a young one; otherwise, the collector would need to scan the entire old generation every time it collects the young heap. A store barrier solves this by checking, on every reference write, whether the destination object is old but the value being stored is young. If that happens, the barrier marks a small region of memory (called a card) containing a possible old-to-young reference. Later, during a young-only GC cycle, ZGC needs to scan only these marked cards instead of the whole old generation, which makes young collections extremely fast. Overall, this improves throughput and lowers latency compared to the original non-generational ZGC.

ZGC parameters

For us users, ZGC is designed to be adaptive and to require minimal manual configuration. During application execution, it dynamically adapts to the workload: resizes generations, scales the number of GC threads, and adjusts tenuring thresholds. The main tuning knob for controlling this garbage collector is to increase the maximum size of the heap.

Here are some important flags that need to be considered for using ZGC and fine-tuning it for the specific use case:

  • -XX:+UseZGC: This is the parameter to use ZGC.

  • -XX:+ZGenerational: Explicitly enables generational mode. Generational ZGC is the default starting Java 23 for which this flag needn't be specified.

  • -Xmx<size>: Used to set the max heap size to control how much memory the JVM is allowed to use for Java objects, and defines the upper limit of the Java heap.

  • -XX:SoftMaxHeapSize=<size>: This argument provides a guideline heap size ZGC attempts to stay below. If necessary, ZGC will go over this limit to avoid allocation problems. ZGC will attempt to get back under the SoftMaxHeapSize as soon as possible and return the memory to the operating system.

  • -Xms<size>: Used to set the minimum heap size. This will stop ZGC from returning unclaimed memory to the operating system, which can cause latency.

  • -XX:-ZUncommit: Used to disable returning memory to the operating system.

  • -XX:ZUncommitDelay=<seconds>: Manages how long ZGC will wait before returning memory to the operating system. The default is 300 seconds.

  • -XX:+AlwaysPreTouch: Moves the preparation of the heap to during startup. This will make startup a little slower, but with the benefit of reducing average latency.

Remember that even though you can adjust different parameters to change the default configurations, it isn't recommended to change them without a detailed performance investigation.

Exploring GC logs

There is one last thing for you to know before you finish this topic. Suppose you need to take a look at the logs of this GC. For that, let's use this code:

public static void main(String[] args) throws InterruptedException {
    for (int i = 1; i < 50_000_000; i++) {
        long[] arr = new long[1_000_000];

        for (int j = 0; j < arr.length; j++) {
            arr[j] = j;
        }

        arr = null;
        System.gc();
    }
}

Now, let's apply the following command to the compiled code:

java -XX:+UseZGC -XX:+ZGenerational -Xmx2024m -Xlog:gc* Main

Below, you see the info of two garbage collection rounds cut from the whole log message.

Log message
[0.010s][info][gc,init] Initializing The Z Garbage Collector
[0.010s][info][gc,init] Version: 24.0.1+9-30 (release)
[0.010s][info][gc,init] NUMA Support: Disabled
[0.010s][info][gc,init] CPUs: 16 total, 16 available
[0.011s][info][gc,init] Memory: 14188M
[0.011s][info][gc,init] Large Page Support: Disabled
[0.011s][info][gc,init] Address Space Type: Contiguous/Unrestricted/Complete
[0.011s][info][gc,init] Address Space Size: 32768M
[0.011s][info][gc,init] Min Capacity: 8M
[0.012s][info][gc,init] Initial Capacity: 222M
[0.012s][info][gc,init] Max Capacity: 2048M
[0.012s][info][gc,init] Soft Max Capacity: 2048M
[0.012s][info][gc,init] Medium Page Size: 32M
[0.012s][info][gc,init] Pre-touch: Disabled
[0.012s][info][gc,init] Uncommit: Enabled
[0.012s][info][gc,init] Uncommit Delay: 300s
[0.013s][info][gc,init] GC Workers for Old Generation: 4 (dynamic)
[0.013s][info][gc,init] GC Workers for Young Generation: 4 (dynamic)
[0.019s][info][gc,init] GC Workers Max: 4 (dynamic)
[0.020s][info][gc,init] Runtime Workers: 10
[0.020s][info][gc     ] Using The Z Garbage Collector
[0.029s][info][gc,metaspace] CDS archive(s) mapped at: [0x000001c1c4000000-0x000001c1c4d40000-0x000001c1c4d40000), size 13893632, SharedBaseAddress: 0x000001c1c4000000, ArchiveRelocationMode: 1.
[0.029s][info][gc,metaspace] Compressed class space mapped at: 0x000001c1c5000000-0x000001c205000000, reserved size: 1073741824
[0.029s][info][gc,metaspace] UseCompressedClassPointers 1, UseCompactObjectHeaders 0
[0.029s][info][gc,metaspace] Narrow klass pointer bits 32, Max shift 3
[0.030s][info][gc,metaspace] Narrow klass base: 0x000001c1c4000000, Narrow klass shift: 0
[0.030s][info][gc,metaspace] Encoding Range: [0x000001c1c4000000 - 0x000001c2c4000000), (4294967296 bytes)
[0.030s][info][gc,metaspace] Klass Range:    [0x000001c1c4000000 - 0x000001c205000000), (1090519040 bytes)
[0.030s][info][gc,metaspace] Klass ID Range:  [8 - 1090519033) (1090519025)
[0.586s][info][gc          ] GC(0) Major Collection (System.gc())
[0.586s][info][gc,task     ] GC(0) Using 4 Workers for Young Generation
[0.587s][info][gc,task     ] GC(0) Using 4 Workers for Old Generation
[0.587s][info][gc,phases   ] GC(0) Y: Young Generation (Promote All)
[0.587s][info][gc,phases   ] GC(0) Y: Pause Mark Start 0.016ms
[0.590s][info][gc,phases   ] GC(0) Y: Concurrent Mark 3.088ms
[0.590s][info][gc,phases   ] GC(0) Y: Pause Mark End 0.015ms
[0.591s][info][gc,phases   ] GC(0) Y: Concurrent Mark Free 0.004ms
[0.591s][info][gc,phases   ] GC(0) Y: Concurrent Reset Relocation Set 0.003ms
[0.598s][info][gc,reloc    ] GC(0) Y: Using tenuring threshold: 0 (Promote All)
[0.599s][info][gc,phases   ] GC(0) Y: Concurrent Select Relocation Set 8.505ms
[0.600s][info][gc,phases   ] GC(0) Y: Pause Relocate Start 0.019ms
[0.603s][info][gc,phases   ] GC(0) Y: Concurrent Relocate 3.257ms
[0.603s][info][gc,alloc    ] GC(0) Y:                         Mark Start        Mark End      Relocate Start    Relocate End
[0.603s][info][gc,alloc    ] GC(0) Y: Allocation Stalls:          0                0                0                0
[0.604s][info][gc,load     ] GC(0) Y: Load: 0.00 (0%) / 0.00 (0%) / 0.00 (0%)
[0.604s][info][gc,mmu      ] GC(0) Y: MMU: 2ms/99.1%, 5ms/99.4%, 10ms/99.7%, 20ms/99.8%, 50ms/99.9%, 100ms/100.0%
[0.604s][info][gc,marking  ] GC(0) Y: Mark: 4 stripe(s), 1 proactive flush(es), 1 terminate flush(es), 0 completion(s), 0 continuation(s)
[0.604s][info][gc,marking  ] GC(0) Y: Mark Stack Usage: 32M
[0.604s][info][gc,nmethod  ] GC(0) Y: NMethods: 1574 registered, 0 unregistered
[0.605s][info][gc,metaspace] GC(0) Y: Metaspace: 9M used, 9M committed, 1088M reserved
[0.605s][info][gc,reloc    ] GC(0) Y:                        Candidates     Selected     In-Place         Size        Empty    Relocated
[0.605s][info][gc,reloc    ] GC(0) Y: Small Pages:                   20           18            0          40M           0M           2M
[0.605s][info][gc,reloc    ] GC(0) Y: Medium Pages:                   1            0            0          32M          32M           0M
[0.605s][info][gc,reloc    ] GC(0) Y: Large Pages:                    1            0            0           8M           8M           0M
[0.605s][info][gc,reloc    ] GC(0) Y: Forwarding Usage: 1M
[0.605s][info][gc,reloc    ] GC(0) Y: Age Table:
[0.605s][info][gc,reloc    ] GC(0) Y:                    Live             Garbage             Small              Medium             Large
[0.606s][info][gc,reloc    ] GC(0) Y: Eden               3M (0%)           76M (4%)          20 / 18             1 / 0              1 / 0
[0.606s][info][gc,heap     ] GC(0) Y: Min Capacity: 8M(0%)
[0.606s][info][gc,heap     ] GC(0) Y: Max Capacity: 2048M(100%)
[0.606s][info][gc,heap     ] GC(0) Y: Soft Max Capacity: 2048M(100%)
[0.606s][info][gc,heap     ] GC(0) Y: Heap Statistics:
[0.606s][info][gc,heap     ] GC(0) Y:                Mark Start          Mark End        Relocate Start      Relocate End           High               Low
[0.606s][info][gc,heap     ] GC(0) Y:  Capacity:      222M (11%)         222M (11%)         222M (11%)         222M (11%)         222M (11%)         222M (11%)
[0.606s][info][gc,heap     ] GC(0) Y:      Free:     1968M (96%)        1968M (96%)        2008M (98%)        2040M (100%)       2040M (100%)       1968M (96%)
[0.607s][info][gc,heap     ] GC(0) Y:      Used:       80M (4%)           80M (4%)           40M (2%)            8M (0%)           80M (4%)            8M (0%)
[0.607s][info][gc,heap     ] GC(0) Y: Young Generation Statistics:
[0.607s][info][gc,heap     ] GC(0) Y:                Mark Start          Mark End        Relocate Start      Relocate End
[0.607s][info][gc,heap     ] GC(0) Y:      Used:       80M (4%)           80M (4%)           40M (2%)            0M (0%)
[0.607s][info][gc,heap     ] GC(0) Y:      Live:         -                 3M (0%)            3M (0%)            0M (0%)
[0.607s][info][gc,heap     ] GC(0) Y:   Garbage:         -                76M (4%)           36M (2%)            0M (0%)
[0.607s][info][gc,heap     ] GC(0) Y: Allocated:         -                 0M (0%)            0M (0%)            0M (0%)
[0.608s][info][gc,heap     ] GC(0) Y: Reclaimed:         -                  -                40M (2%)           76M (4%)
[0.608s][info][gc,heap     ] GC(0) Y:  Promoted:         -                  -                 0M (0%)            3M (0%)
[0.608s][info][gc,heap     ] GC(0) Y: Compacted:         -                  -                  -                 0M (0%)
[0.608s][info][gc,phases   ] GC(0) Y: Young Generation (Promote All) 80M(4%)->8M(0%) 0.016s
[0.608s][info][gc,phases   ] GC(0) Y: Young Generation (Collect Roots)
[0.608s][info][gc,phases   ] GC(0) Y: Pause Mark Start (Major) 0.019ms
[0.610s][info][gc,phases   ] GC(0) Y: Concurrent Mark 1.972ms
[0.611s][info][gc,phases   ] GC(0) Y: Pause Mark End 0.014ms
[0.611s][info][gc,phases   ] GC(0) Y: Concurrent Mark Free 0.152ms
[0.611s][info][gc,phases   ] GC(0) Y: Concurrent Reset Relocation Set 0.002ms
[0.613s][info][gc,reloc    ] GC(0) Y: Using tenuring threshold: 0 (Computed)
[0.613s][info][gc,phases   ] GC(0) Y: Concurrent Select Relocation Set 1.793ms
[0.613s][info][gc,phases   ] GC(0) Y: Pause Relocate Start 0.009ms
[0.613s][info][gc,phases   ] GC(0) Y: Concurrent Relocate 0.077ms
[0.614s][info][gc,alloc    ] GC(0) Y:                         Mark Start        Mark End      Relocate Start    Relocate End
[0.614s][info][gc,alloc    ] GC(0) Y: Allocation Stalls:          0                0                0                0
[0.614s][info][gc,load     ] GC(0) Y: Load: 0.00 (0%) / 0.00 (0%) / 0.00 (0%)
[0.614s][info][gc,mmu      ] GC(0) Y: MMU: 2ms/99.0%, 5ms/99.1%, 10ms/99.6%, 20ms/99.7%, 50ms/99.8%, 100ms/99.9%
[0.614s][info][gc,marking  ] GC(0) Y: Mark: 4 stripe(s), 1 proactive flush(es), 1 terminate flush(es), 0 completion(s), 0 continuation(s)
[0.614s][info][gc,marking  ] GC(0) Y: Mark Stack Usage: 0M
[0.614s][info][gc,nmethod  ] GC(0) Y: NMethods: 1574 registered, 0 unregistered
[0.614s][info][gc,metaspace] GC(0) Y: Metaspace: 9M used, 9M committed, 1088M reserved
[0.615s][info][gc,heap     ] GC(0) Y: Min Capacity: 8M(0%)
[0.615s][info][gc,heap     ] GC(0) Y: Max Capacity: 2048M(100%)
[0.615s][info][gc,heap     ] GC(0) Y: Soft Max Capacity: 2048M(100%)
[0.615s][info][gc,heap     ] GC(0) Y: Heap Statistics:
[0.615s][info][gc,heap     ] GC(0) Y:                Mark Start          Mark End        Relocate Start      Relocate End           High               Low
[0.615s][info][gc,heap     ] GC(0) Y:  Capacity:      222M (11%)         222M (11%)         222M (11%)         222M (11%)         222M (11%)         222M (11%)
[0.615s][info][gc,heap     ] GC(0) Y:      Free:     2040M (100%)       2040M (100%)       2040M (100%)       2040M (100%)       2040M (100%)       2040M (100%)
[0.616s][info][gc,heap     ] GC(0) Y:      Used:        8M (0%)            8M (0%)            8M (0%)            8M (0%)            8M (0%)            8M (0%)
[0.616s][info][gc,heap     ] GC(0) Y: Young Generation Statistics:
[0.616s][info][gc,heap     ] GC(0) Y:                Mark Start          Mark End        Relocate Start      Relocate End
[0.616s][info][gc,heap     ] GC(0) Y:      Used:        0M (0%)            0M (0%)            0M (0%)            0M (0%)
[0.616s][info][gc,heap     ] GC(0) Y:      Live:         -                 0M (0%)            0M (0%)            0M (0%)
[0.616s][info][gc,heap     ] GC(0) Y:   Garbage:         -                 0M (0%)            0M (0%)            0M (0%)
[0.616s][info][gc,heap     ] GC(0) Y: Allocated:         -                 0M (0%)            0M (0%)            0M (0%)
[0.616s][info][gc,heap     ] GC(0) Y: Reclaimed:         -                  -                 0M (0%)            0M (0%)
[0.617s][info][gc,heap     ] GC(0) Y:  Promoted:         -                  -                 0M (0%)            0M (0%)
[0.617s][info][gc,heap     ] GC(0) Y: Compacted:         -                  -                  -                 0M (0%)
[0.617s][info][gc,phases   ] GC(0) Y: Young Generation (Collect Roots) 8M(0%)->8M(0%) 0.005s
[0.617s][info][gc,phases   ] GC(0) O: Old Generation
[0.619s][info][gc,phases   ] GC(0) O: Concurrent Mark 2.102ms
[0.619s][info][gc,phases   ] GC(0) O: Pause Mark End 0.021ms
[0.620s][info][gc,phases   ] GC(0) O: Concurrent Mark Free 0.001ms
[0.621s][info][gc,phases   ] GC(0) O: Concurrent Process Non-Strong 1.226ms
[0.621s][info][gc,phases   ] GC(0) O: Concurrent Reset Relocation Set 0.001ms
[0.623s][info][gc,phases   ] GC(0) O: Concurrent Select Relocation Set 1.823ms
[0.623s][info][gc,task     ] GC(0) O: Using 4 Workers for Old Generation
[0.656s][info][gc,task     ] GC(0) O: Using 4 Workers for Old Generation
[0.656s][info][gc,phases   ] GC(0) O: Concurrent Remap Roots 32.956ms
[0.656s][info][gc,phases   ] GC(0) O: Pause Relocate Start 0.014ms
[0.659s][info][gc,phases   ] GC(0) O: Concurrent Relocate 1.784ms
[0.659s][info][gc,alloc    ] GC(0) O:                         Mark Start        Mark End      Relocate Start    Relocate End
[0.659s][info][gc,alloc    ] GC(0) O: Allocation Stalls:          0                0                0                0
[0.659s][info][gc,load     ] GC(0) O: Load: 0.00 (0%) / 0.00 (0%) / 0.00 (0%)
[0.659s][info][gc,mmu      ] GC(0) O: MMU: 2ms/98.9%, 5ms/99.1%, 10ms/99.6%, 20ms/99.6%, 50ms/99.8%, 100ms/99.9%
[0.659s][info][gc,marking  ] GC(0) O: Mark: 4 stripe(s), 2 proactive flush(es), 1 terminate flush(es), 0 completion(s), 0 continuation(s)
[0.660s][info][gc,marking  ] GC(0) O: Mark Stack Usage: 32M
[0.660s][info][gc,nmethod  ] GC(0) O: NMethods: 1412 registered, 163 unregistered
[0.660s][info][gc,metaspace] GC(0) O: Metaspace: 9M used, 9M committed, 1088M reserved
[0.660s][info][gc,ref      ] GC(0) O:                       Encountered   Discovered     Enqueued
[0.660s][info][gc,ref      ] GC(0) O: Soft References:              167            0            0
[0.660s][info][gc,ref      ] GC(0) O: Weak References:              990          258          253
[0.660s][info][gc,ref      ] GC(0) O: Final References:               0            0            0
[0.661s][info][gc,ref      ] GC(0) O: Phantom References:             3            3            0
[0.661s][info][gc,reloc    ] GC(0) O:                        Candidates     Selected     In-Place         Size        Empty    Relocated
[0.661s][info][gc,reloc    ] GC(0) O: Small Pages:                    4            2            0           8M           0M           1M
[0.661s][info][gc,reloc    ] GC(0) O: Medium Pages:                   0            0            0           0M           0M           0M
[0.661s][info][gc,reloc    ] GC(0) O: Large Pages:                    0            0            0           0M           0M           0M
[0.661s][info][gc,reloc    ] GC(0) O: Forwarding Usage: 1M
[0.661s][info][gc,heap     ] GC(0) O: Min Capacity: 8M(0%)
[0.662s][info][gc,heap     ] GC(0) O: Max Capacity: 2048M(100%)
[0.662s][info][gc,heap     ] GC(0) O: Soft Max Capacity: 2048M(100%)
[0.662s][info][gc,heap     ] GC(0) O: Heap Statistics:
[0.662s][info][gc,heap     ] GC(0) O:                Mark Start          Mark End        Relocate Start      Relocate End           High               Low
[0.662s][info][gc,heap     ] GC(0) O:  Capacity:      222M (11%)         222M (11%)         222M (11%)         222M (11%)         222M (11%)         222M (11%)
[0.662s][info][gc,heap     ] GC(0) O:      Free:     2040M (100%)       2040M (100%)       2040M (100%)       2040M (100%)       2040M (100%)       2032M (99%)
[0.662s][info][gc,heap     ] GC(0) O:      Used:        8M (0%)            8M (0%)            8M (0%)            8M (0%)           16M (1%)            8M (0%)
[0.663s][info][gc,heap     ] GC(0) O: Old Generation Statistics:
[0.663s][info][gc,heap     ] GC(0) O:                Mark Start          Mark End        Relocate Start      Relocate End
[0.663s][info][gc,heap     ] GC(0) O:      Used:        8M (0%)            8M (0%)            8M (0%)            8M (0%)
[0.663s][info][gc,heap     ] GC(0) O:      Live:         -                 2M (0%)            2M (0%)            2M (0%)
[0.663s][info][gc,heap     ] GC(0) O:   Garbage:         -                 5M (0%)            5M (0%)            0M (0%)
[0.663s][info][gc,heap     ] GC(0) O: Allocated:         -                 0M (0%)            0M (0%)            5M (0%)
[0.663s][info][gc,heap     ] GC(0) O: Reclaimed:         -                  -                 0M (0%)            5M (0%)
[0.664s][info][gc,heap     ] GC(0) O: Compacted:         -                  -                  -                 2M (0%)
[0.664s][info][gc,phases   ] GC(0) O: Old Generation 8M(0%)->8M(0%) 0.042s
[0.664s][info][gc          ] GC(0) Major Collection (System.gc()) 80M(4%)->8M(0%) 0.078s
[0.670s][info][gc          ] GC(1) Major Collection (System.gc())
[0.670s][info][gc,task     ] GC(1) Using 4 Workers for Young Generation
[0.670s][info][gc,task     ] GC(1) Using 4 Workers for Old Generation
[0.670s][info][gc,phases   ] GC(1) Y: Young Generation (Promote All)
[0.671s][info][gc,phases   ] GC(1) Y: Pause Mark Start 0.017ms
[0.672s][info][gc,phases   ] GC(1) Y: Concurrent Mark 1.492ms
[0.672s][info][gc,phases   ] GC(1) Y: Pause Mark End 0.010ms
[0.673s][info][gc,phases   ] GC(1) Y: Concurrent Mark Free 0.001ms
[0.673s][info][gc,phases   ] GC(1) Y: Concurrent Reset Relocation Set 0.001ms
[0.674s][info][gc,reloc    ] GC(1) Y: Using tenuring threshold: 0 (Promote All)
[0.675s][info][gc,phases   ] GC(1) Y: Concurrent Select Relocation Set 1.715ms
[0.675s][info][gc,phases   ] GC(1) Y: Pause Relocate Start 0.016ms
[0.675s][info][gc,phases   ] GC(1) Y: Concurrent Relocate 0.082ms
[0.675s][info][gc,alloc    ] GC(1) Y:                         Mark Start        Mark End      Relocate Start    Relocate End
[0.675s][info][gc,alloc    ] GC(1) Y: Allocation Stalls:          0                0                0                0
[0.676s][info][gc,load     ] GC(1) Y: Load: 0.00 (0%) / 0.00 (0%) / 0.00 (0%)
[0.676s][info][gc,mmu      ] GC(1) Y: MMU: 2ms/98.6%, 5ms/99.1%, 10ms/99.6%, 20ms/99.6%, 50ms/99.8%, 100ms/99.8%
[0.676s][info][gc,marking  ] GC(1) Y: Mark: 4 stripe(s), 1 proactive flush(es), 1 terminate flush(es), 0 completion(s), 0 continuation(s)
[0.676s][info][gc,marking  ] GC(1) Y: Mark Stack Usage: 0M
[0.676s][info][gc,nmethod  ] GC(1) Y: NMethods: 1412 registered, 163 unregistered
[0.676s][info][gc,metaspace] GC(1) Y: Metaspace: 9M used, 9M committed, 1088M reserved
[0.676s][info][gc,reloc    ] GC(1) Y:                        Candidates     Selected     In-Place         Size        Empty    Relocated
[0.677s][info][gc,reloc    ] GC(1) Y: Small Pages:                    0            0            0           0M           0M           0M
[0.677s][info][gc,reloc    ] GC(1) Y: Medium Pages:                   0            0            0           0M           0M           0M
[0.677s][info][gc,reloc    ] GC(1) Y: Large Pages:                    1            0            0           8M           8M           0M
[0.677s][info][gc,reloc    ] GC(1) Y: Forwarding Usage: 0M
[0.677s][info][gc,reloc    ] GC(1) Y: Age Table:
[0.677s][info][gc,reloc    ] GC(1) Y:                    Live             Garbage             Small              Medium             Large
[0.677s][info][gc,reloc    ] GC(1) Y: Eden                -                 8M (0%)           0 / 0              0 / 0              1 / 0
[0.677s][info][gc,heap     ] GC(1) Y: Min Capacity: 8M(0%)
[0.678s][info][gc,heap     ] GC(1) Y: Max Capacity: 2048M(100%)
[0.678s][info][gc,heap     ] GC(1) Y: Soft Max Capacity: 2048M(100%)
[0.678s][info][gc,heap     ] GC(1) Y: Heap Statistics:
[0.678s][info][gc,heap     ] GC(1) Y:                Mark Start          Mark End        Relocate Start      Relocate End           High               Low
[0.678s][info][gc,heap     ] GC(1) Y:  Capacity:      222M (11%)         222M (11%)         222M (11%)         222M (11%)         222M (11%)         222M (11%)
[0.678s][info][gc,heap     ] GC(1) Y:      Free:     2032M (99%)        2032M (99%)        2040M (100%)       2040M (100%)       2040M (100%)       2032M (99%)
[0.678s][info][gc,heap     ] GC(1) Y:      Used:       16M (1%)           16M (1%)            8M (0%)            8M (0%)           16M (1%)            8M (0%)
[0.678s][info][gc,heap     ] GC(1) Y: Young Generation Statistics:
[0.678s][info][gc,heap     ] GC(1) Y:                Mark Start          Mark End        Relocate Start      Relocate End
[0.679s][info][gc,heap     ] GC(1) Y:      Used:        8M (0%)            8M (0%)            0M (0%)            0M (0%)
[0.679s][info][gc,heap     ] GC(1) Y:      Live:         -                 0M (0%)            0M (0%)            0M (0%)
[0.679s][info][gc,heap     ] GC(1) Y:   Garbage:         -                 8M (0%)            0M (0%)            0M (0%)
[0.679s][info][gc,heap     ] GC(1) Y: Allocated:         -                 0M (0%)            0M (0%)            0M (0%)
[0.679s][info][gc,heap     ] GC(1) Y: Reclaimed:         -                  -                 8M (0%)            8M (0%)
[0.679s][info][gc,heap     ] GC(1) Y:  Promoted:         -                  -                 0M (0%)            0M (0%)
[0.679s][info][gc,heap     ] GC(1) Y: Compacted:         -                  -                  -                 0M (0%)
[0.680s][info][gc,phases   ] GC(1) Y: Young Generation (Promote All) 16M(1%)->8M(0%) 0.005s
[0.680s][info][gc,phases   ] GC(1) Y: Young Generation (Collect Roots)
[0.680s][info][gc,phases   ] GC(1) Y: Pause Mark Start (Major) 0.011ms
[0.681s][info][gc,phases   ] GC(1) Y: Concurrent Mark 1.090ms
[0.681s][info][gc,phases   ] GC(1) Y: Pause Mark End 0.008ms
[0.681s][info][gc,phases   ] GC(1) Y: Concurrent Mark Free 0.001ms
[0.682s][info][gc,phases   ] GC(1) Y: Concurrent Reset Relocation Set 0.000ms
[0.683s][info][gc,reloc    ] GC(1) Y: Using tenuring threshold: 0 (Computed)
[0.683s][info][gc,phases   ] GC(1) Y: Concurrent Select Relocation Set 1.478ms
[0.683s][info][gc,phases   ] GC(1) Y: Pause Relocate Start 0.007ms
[0.684s][info][gc,phases   ] GC(1) Y: Concurrent Relocate 0.072ms
[0.684s][info][gc,alloc    ] GC(1) Y:                         Mark Start        Mark End      Relocate Start    Relocate End
[0.684s][info][gc,alloc    ] GC(1) Y: Allocation Stalls:          0                0                0                0         
[0.684s][info][gc,load     ] GC(1) Y: Load: 0.00 (0%) / 0.00 (0%) / 0.00 (0%)
[0.684s][info][gc,mmu      ] GC(1) Y: MMU: 2ms/98.6%, 5ms/99.1%, 10ms/99.5%, 20ms/99.6%, 50ms/99.8%, 100ms/99.8%
[0.684s][info][gc,marking  ] GC(1) Y: Mark: 4 stripe(s), 1 proactive flush(es), 1 terminate flush(es), 0 completion(s), 0 continuation(s)
[0.684s][info][gc,marking  ] GC(1) Y: Mark Stack Usage: 0M
[0.685s][info][gc,nmethod  ] GC(1) Y: NMethods: 1412 registered, 163 unregistered
[0.685s][info][gc,metaspace] GC(1) Y: Metaspace: 9M used, 9M committed, 1088M reserved
[0.685s][info][gc,heap     ] GC(1) Y: Min Capacity: 8M(0%)
[0.685s][info][gc,heap     ] GC(1) Y: Max Capacity: 2048M(100%)
[0.685s][info][gc,heap     ] GC(1) Y: Soft Max Capacity: 2048M(100%)
[0.685s][info][gc,heap     ] GC(1) Y: Heap Statistics:
[0.685s][info][gc,heap     ] GC(1) Y:                Mark Start          Mark End        Relocate Start      Relocate End           High               Low
[0.686s][info][gc,heap     ] GC(1) Y:  Capacity:      222M (11%)         222M (11%)         222M (11%)         222M (11%)         222M (11%)         222M (11%)
[0.686s][info][gc,heap     ] GC(1) Y:      Free:     2040M (100%)       2040M (100%)       2040M (100%)       2040M (100%)       2040M (100%)       2040M (100%)
[0.686s][info][gc,heap     ] GC(1) Y:      Used:        8M (0%)            8M (0%)            8M (0%)            8M (0%)            8M (0%)            8M (0%)
[0.686s][info][gc,heap     ] GC(1) Y: Young Generation Statistics:
[0.686s][info][gc,heap     ] GC(1) Y:                Mark Start          Mark End        Relocate Start      Relocate End
[0.686s][info][gc,heap     ] GC(1) Y:      Used:        0M (0%)            0M (0%)            0M (0%)            0M (0%)
[0.686s][info][gc,heap     ] GC(1) Y:      Live:         -                 0M (0%)            0M (0%)            0M (0%)
[0.686s][info][gc,heap     ] GC(1) Y:   Garbage:         -                 0M (0%)            0M (0%)            0M (0%)
[0.687s][info][gc,heap     ] GC(1) Y: Allocated:         -                 0M (0%)            0M (0%)            0M (0%)
[0.687s][info][gc,heap     ] GC(1) Y: Reclaimed:         -                  -                 0M (0%)            0M (0%)
[0.687s][info][gc,heap     ] GC(1) Y:  Promoted:         -                  -                 0M (0%)            0M (0%)
[0.687s][info][gc,heap     ] GC(1) Y: Compacted:         -                  -                  -                 0M (0%)
[0.687s][info][gc,phases   ] GC(1) Y: Young Generation (Collect Roots) 8M(0%)->8M(0%) 0.004s
[0.687s][info][gc,phases   ] GC(1) O: Old Generation
[0.690s][info][gc,phases   ] GC(1) O: Concurrent Mark 3.247ms
[0.691s][info][gc,phases   ] GC(1) O: Pause Mark End 0.021ms
[0.691s][info][gc,phases   ] GC(1) O: Concurrent Mark Free 0.001ms
[0.692s][info][gc,phases   ] GC(1) O: Concurrent Process Non-Strong 0.834ms
[0.692s][info][gc,phases   ] GC(1) O: Concurrent Reset Relocation Set 0.001ms
[0.694s][info][gc,phases   ] GC(1) O: Concurrent Select Relocation Set 1.987ms
[0.694s][info][gc,task     ] GC(1) O: Using 4 Workers for Old Generation
[0.727s][info][gc,task     ] GC(1) O: Using 4 Workers for Old Generation
[0.727s][info][gc,phases   ] GC(1) O: Concurrent Remap Roots 32.656ms
[0.727s][info][gc,phases   ] GC(1) O: Pause Relocate Start 0.014ms
[0.729s][info][gc,phases   ] GC(1) O: Concurrent Relocate 2.016ms
[0.730s][info][gc,alloc    ] GC(1) O:                         Mark Start        Mark End      Relocate Start    Relocate End
[0.730s][info][gc,alloc    ] GC(1) O: Allocation Stalls:          0                0                0                0
[0.730s][info][gc,load     ] GC(1) O: Load: 0.00 (0%) / 0.00 (0%) / 0.00 (0%)
[0.730s][info][gc,mmu      ] GC(1) O: MMU: 2ms/98.6%, 5ms/99.1%, 10ms/99.5%, 20ms/99.6%, 50ms/99.8%, 100ms/99.8%
[0.730s][info][gc,marking  ] GC(1) O: Mark: 4 stripe(s), 2 proactive flush(es), 1 terminate flush(es), 0 completion(s), 0 continuation(s)
[0.730s][info][gc,marking  ] GC(1) O: Mark Stack Usage: 32M
[0.730s][info][gc,nmethod  ] GC(1) O: NMethods: 1410 registered, 165 unregistered
[0.731s][info][gc,metaspace] GC(1) O: Metaspace: 9M used, 9M committed, 1088M reserved
[0.731s][info][gc,ref      ] GC(1) O:                       Encountered   Discovered     Enqueued
[0.731s][info][gc,ref      ] GC(1) O: Soft References:              167            0            0
[0.731s][info][gc,ref      ] GC(1) O: Weak References:              737          317            0
[0.731s][info][gc,ref      ] GC(1) O: Final References:               0            0            0
[0.731s][info][gc,ref      ] GC(1) O: Phantom References:             3            3            0
[0.732s][info][gc,reloc    ] GC(1) O:                        Candidates     Selected     In-Place         Size        Empty    Relocated
[0.732s][info][gc,reloc    ] GC(1) O: Small Pages:                    4            2            0           8M           0M           1M
[0.732s][info][gc,reloc    ] GC(1) O: Medium Pages:                   0            0            0           0M           0M           0M
[0.732s][info][gc,reloc    ] GC(1) O: Large Pages:                    0            0            0           0M           0M           0M
[0.733s][info][gc,reloc    ] GC(1) O: Forwarding Usage: 1M
[0.733s][info][gc,heap     ] GC(1) O: Min Capacity: 8M(0%)
[0.733s][info][gc,heap     ] GC(1) O: Max Capacity: 2048M(100%)
[0.733s][info][gc,heap     ] GC(1) O: Soft Max Capacity: 2048M(100%)
[0.733s][info][gc,heap     ] GC(1) O: Heap Statistics:
[0.733s][info][gc,heap     ] GC(1) O:                Mark Start          Mark End        Relocate Start      Relocate End           High               Low
[0.734s][info][gc,heap     ] GC(1) O:  Capacity:      222M (11%)         222M (11%)         222M (11%)         222M (11%)         222M (11%)         222M (11%)
[0.734s][info][gc,heap     ] GC(1) O:      Free:     2040M (100%)       2040M (100%)       2040M (100%)       2040M (100%)       2040M (100%)       2032M (99%)
[0.734s][info][gc,heap     ] GC(1) O:      Used:        8M (0%)            8M (0%)            8M (0%)            8M (0%)           16M (1%)            8M (0%)
[0.734s][info][gc,heap     ] GC(1) O: Old Generation Statistics:
[0.734s][info][gc,heap     ] GC(1) O:                Mark Start          Mark End        Relocate Start      Relocate End
[0.734s][info][gc,heap     ] GC(1) O:      Used:        8M (0%)            8M (0%)            8M (0%)            8M (0%)
[0.734s][info][gc,heap     ] GC(1) O:      Live:         -                 2M (0%)            2M (0%)            2M (0%)
[0.735s][info][gc,heap     ] GC(1) O:   Garbage:         -                 5M (0%)            5M (0%)            0M (0%)
[0.735s][info][gc,heap     ] GC(1) O: Allocated:         -                 0M (0%)            0M (0%)            5M (0%)
[0.735s][info][gc,heap     ] GC(1) O: Reclaimed:         -                  -                 0M (0%)            5M (0%)
[0.735s][info][gc,heap     ] GC(1) O: Compacted:         -                  -                  -                 2M (0%)
[0.735s][info][gc,phases   ] GC(1) O: Old Generation 8M(0%)->8M(0%) 0.043s
[0.735s][info][gc          ] GC(1) Major Collection (System.gc()) 16M(1%)->8M(0%) 0.065s

When you examine the log, you’ll see entries labelled gc with phases like Mark, Relocate, Pause, Heap Statistics, etc. Look for:

  • Pause times (should be very small, often micro- to low-millisecond)

  • Heap before/after and region counts

  • Young vs old generation region counts

  • Concurrent vs STW markers

As you examine the log, you’ll see that frequent young collections occur without scanning the entire old generation, leading to better throughput and memory utilization in many cases.

Conclusion

In short, Generational ZGC builds on the foundation of ZGC’s concurrent, low-latency design, but optimizes it for real-world workloads by collecting short-lived objects more often and reducing overall GC overhead. It preserves what made ZGC special — sub-millisecond pauses independent of heap size — while improving throughput and efficiency for typical Java applications.

For many workloads, simply using ZGC is enough to solve all latency problems related to garbage collection. This works well as long as there are sufficient resources (i.e., memory and CPU) available to ensure that ZGC can reclaim memory faster than the concurrently-running application threads consume it.

References

[1] Oracle, "Deep-dive of ZGC's Architecture," dev.java (accessed Jan. 21, 2026)

3 learners liked this piece of theory. 0 didn't like it. What about you?
Report a typo