Welcome back! In our last lesson, we unpacked the foundational Mark-Sweep-Compact algorithm, which forms the basis of Java's garbage collection. We saw that while this process effectively reclaims memory, the "stop-the-world" pauses required for sweeping and especially for compacting can be a major performance bottleneck for modern applications. For an SDET like yourself, who architects and analyzes the performance of complex systems, understanding how to mitigate these pauses is crucial.
Today, we will build directly on that foundation to explore the modern garbage collectors that are central to Java's performance in enterprise environments. This is a topic that frequently appears in senior engineering and architecture interviews. We will compare the high-level mechanisms and trade-offs of the two most prominent collectors: the Garbage-First (G1) GC, which is the default in modern JVMs, and the Z Garbage Collector (ZGC), designed for applications with extremely low latency requirements.
Beyond Simple Stop-the-World
The simple Serial and Parallel GCs, which largely follow the Mark-Sweep-Compact model, halt all application threads to perform their work. While effective for applications with small heaps or where throughput is the only concern, they are unsuitable for the responsive, low-latency services common today. This led to the development of collectors that perform more work concurrently with the application.
An early attempt was the Concurrent Mark Sweep (CMS) collector, but it suffered from issues like memory fragmentation and has since been deprecated. Its successor, and the current default, is G1.
G1 GC: The Balanced Workhorse
The Garbage-First (G1) collector was designed to be the successor to CMS, providing a better balance between latency (pause times) and throughput. It has been the default GC since Java 9 and is an excellent choice for most server-side applications running on multi-processor machines with large heaps.
The following video from the official Java YouTube channel gives a superb overview of why G1 was created and its core design principles.
This video explains the motivation behind G1 and introduces its core concepts like the region-based heap.
Watch the segments that cover G1's design goals and its generational, incremental nature. Pay close attention to the explanation of the region-based heap, as this is the fundamental difference from older collectors. G1's design goals Generational and incremental
As the video explained, G1's power comes from a few key architectural innovations that depart from the simple, contiguous Young and Old generations we discussed previously.
1. Region-Based Heap Layout
Instead of two large, contiguous memory blocks, G1 partitions the entire heap into a large number of smaller, equal-sized regions (typically 1MB to 32MB).

This regional structure gives G1 flexibility. The Young Generation is no longer a single block of memory but a collection of regions. This allows the GC to resize the Young Generation easily by adding or removing regions, adapting to the application's behavior.
There is also a special region type called Humongous for objects larger than 50% of a region's size. These are typically allocated directly in the Old generation space as large, contiguous regions.
2. The "Garbage-First" Principle
The name "Garbage-First" comes from how G1 chooses which parts of the heap to clean. Instead of a "Major GC" that cleans the entire Old Generation, G1 performs Mixed Collections. In a mixed collection, G1 collects:
- All of the Young Generation regions (this is always the case, similar to a Minor GC).
- A selection of Old Generation regions.
Crucially, G1 prioritizes collecting the Old regions that contain the most garbage. It maintains a "liveness" percentage for each region and adds the ones with the most reclaimable space to its collection set. This approach allows G1 to meet user-defined pause time goals (e.g., -XX:MaxGCPauseMillis=200) by limiting the number of Old regions it processes in any single pause.
3. Tracking Cross-Region References
A key challenge with a region-based heap is handling references between regions. If G1 is collecting a Young region, how does it know if an object inside is still "live" because it's referenced by an object in an Old region, without scanning the entire Old generation?
G1 solves this using two main components:
- Remembered Sets (RSets): Each region has an RSet, which is a data structure that tracks incoming references from other regions. When collecting a region, G1 only needs to scan its RSet to find any external references, rather than the entire heap.
- Write Barriers: This is a mechanism that intercepts reference assignment operations in your application code (e.g.,
myObject.field = anotherObject;). When a write creates a cross-region reference (e.g., an Old object pointing to a Young object), the write barrier code updates the RSet for the target region.
For a deeper dive into G1's internal structures, the following article provides excellent detail.
A Detailed Explanation of JVM Garbage Collector G1&ZGC
This technical article from Alibaba Cloud goes into the weeds of G1's implementation. We'll focus on the sections describing its unique memory layout and reference tracking.
First, read the subsection Region Memory Division to solidify your understanding of regions and the Humongous type. Next, read the subsection on Remember Set. This explains how RSets and card tables are used to solve the problem of cross-generation references efficiently. This level of detail is often what interviewers are looking for.
In summary, G1 balances throughput and latency by using a region-based heap and focusing its paused collection work on the areas with the most garbage, all while using RSets and write barriers to concurrently track object liveness.
ZGC: The Ultra-Low-Latency Collector
While G1 provides predictable pause times, some applications require pauses to be almost non-existent. Think of high-frequency trading platforms, real-time analytics, or latency-sensitive microservices where a 200ms pause is unacceptable. For these use cases, Oracle developed the Z Garbage Collector (ZGC).
ZGC's primary design goal is to achieve extremely low and predictable pause times, typically under 10 milliseconds and often in the sub-millisecond range, regardless of heap size—even on heaps of multiple terabytes.
All 7 Java Garbage Collectors Explained | Serial, Parallel, G1, ZGC, Shenandoah & More
Let's get a quick introduction to ZGC from the "All 7 Java Garbage Collectors Explained" video.
Watch the section on ZGC, focusing on its core promise and target use case from this timestamp.
ZGC achieves this ambitious goal by performing nearly all its work—including the expensive compaction phase—concurrently with the application threads. This is made possible by two ingenious technologies: Colored Pointers and Load Barriers.
1. Colored Pointers
On a 64-bit system, a memory address doesn't actually need all 64 bits to point to memory (current hardware supports up to 48 bits of address space, which is still a massive 256TB). ZGC uses some of these spare bits in the pointer itself to store metadata about the object's state.
These "colored" bits can tell the GC if an object has been marked as live or if its reference needs to be updated. As seen in the diagram above, this is a core part of ZGC's design.
2. Load Barriers (or Read Barriers)
This is the magic that allows ZGC to move objects (compact memory) while the application is running. A load barrier is a small piece of code that the JVM injects whenever your application reads an object reference from the heap.
Here's how it works:
- ZGC decides to move an object from address A to a new address B.
- Your application thread tries to read the object at address A.
- The load barrier code executes. It checks the colored bits in the pointer for address A.
- The bits indicate that the object is being relocated. The load barrier looks up the new address B in a forwarding table.
- It then updates the reference your application was using to point to B instead of A. This is called self-healing.
- Finally, it returns the object from the new address B to your application.
This process is transparent to your application, but it means that the application threads themselves help finish the GC work on-the-fly, eliminating the need for a long stop-the-world pause to update all references.
A Detailed Explanation of JVM Garbage Collector G1&ZGC
Let's return to the Alibaba Cloud article for a detailed explanation of ZGC's core technologies.
Read the section on ZGC, starting with the explanation of Colored Pointers and Read Barriers. Focus on understanding how these two technologies work together to enable concurrent relocation.
The trade-off for this incredibly low latency is a slight reduction in application throughput, as the load barrier code adds a small overhead to every object reference read from the heap.
G1 vs. ZGC: A Comparison of Trade-offs
Choosing a garbage collector is about understanding the needs of your application and making the right trade-off.

Here’s a summary of the key differences:
| Feature | G1 (Garbage-First) GC | ZGC (Z Garbage Collector) |
|---|---|---|
| Primary Goal | Balance of throughput and predictable pause times. | Ultra-low, predictable latency (sub-10ms pauses). |
| Typical Use Case | Most server-side applications with heaps > 6-8 GB. | Latency-sensitive systems (e.g., trading, real-time data). |
| Pause Nature | Short stop-the-world pauses for evacuation (compaction). | Pauses are extremely short (sub-ms), not correlated with heap size. |
| Concurrency | Concurrent marking, but paused evacuation. | Concurrent marking, relocation, and reference updating. |
| Key Technology | Region-based heap, Remembered Sets, Write Barriers. | Colored Pointers, Load Barriers. |
| Throughput | High. | Lower than G1 due to load barrier overhead. |
| Heap Size | Large (up to tens of GBs). | Very Large (from GBs to multi-terabytes). |
In essence:
- Use G1 (the default) when you need good overall performance for a typical server application and can tolerate pauses in the range of tens or a few hundred milliseconds.
- Use ZGC when application responsiveness is critical, and even short GC pauses are unacceptable. You accept a small throughput penalty to gain near-pause-less operation.
Conclusion
You now have a solid understanding of how modern Java garbage collectors work and the design decisions that differentiate them. This knowledge is not only vital for building high-performance applications but is also a hallmark of a senior developer in technical interviews.
Here are the key takeaways:
- Modern GCs aim to minimize "stop-the-world" pauses by performing work concurrently.
- G1 GC is the default, balanced collector. It uses a region-based heap and mixed collections to achieve predictable pause times by collecting regions with the most garbage first. It relies on Remembered Sets and Write Barriers to track references concurrently.
- ZGC is an ultra-low-latency collector. It achieves sub-millisecond pauses by performing nearly all work concurrently, including compaction. This is enabled by its use of Colored Pointers and Load Barriers, which allow for "self-healing" of references while the application is running.
- The choice between G1 and ZGC is a classic engineering trade-off between throughput and latency.
We've now completed our deep dive into JVM memory management and garbage collection. However, even with automatic GC, applications can still suffer from memory problems. In our next lesson, we will investigate the concept of a memory leak in Java and learn how to take the first step in diagnosing one by generating a heap dump.