1. Introduction

Managing temporary objects is a crucial performance factor in Java virtual machine optimization. With the introduction of Generational ZGC (Z Garbage Collector) in Java 21 (now available in Java 22), we enter a new era of memory management to handle short-lived objects efficiently.

In this tutorial, we’ll explore Generational ZGC in detail and discuss how it can enhance the management of temporary objects. We’ll show how it can contribute to the overall improvement in JVM performance. Finally, we’ll share a practical use case that can benefit from our newfound knowledge.

2. Temporary Objects in Java

Temporary or short-lived objects are objects created during the program execution with a short lifetime.

In general, such objects are intermediate results, methods parameters, or temporary data structures. In many Java programs, a large fraction of all objects fall into this category. This is particularly true in applications involving frequent method calls, temporary computations, or rapid data transformations.

As an example, we’ll consider the lifecycle of temporary objects in Java, particularly in the context of web requests. This includes their creation, processing, and eventual disposal during client-server interactions.

The client first communicates with the server by sending an HTTP request to the server which prompts the server to generate a temporary request object to be processed. While the server processes the request, it may create additional temporary objects for computations and data management (such as accessing or modifying session data held as temporary objects).

Upon completion, the server returns an HTTP response to the client encapsulated in a temporary response object. Finally, once the response is received, the server destroys the temporary objects generated during the request to free up memory.

The diagram below represents the lifecycle of temporary objects in the context of web requests:

temporary objects example

It outlines the steps involved in the lifecycle of temporary objects during a web request, from the initial user action to the cleanup of temporary objects after processing.

3. The Evolution of ZGC

The Z Garbage Collector, which started in Java 11, is the most recent milestone in this evolution of JVM garbage collection. ZGC has low latency and scalability characteristics, making it a good choice for low-latency server workloads.

Each subsequent Java release has enhanced ZGC with additional optimizations. In April 2023, after a preview, Generational ZGC was launched in Java 21 to improve temporary object management.

4. How Generational ZGC Works

**Generational ZGC divides the heap into two segments: one for newly created objects (young generation) and another for objects with longer lifespans (old generation).
**

4.1. Object Allocation and Young Generation Collection

Young objects are initially allocated in the young generation of the Generational Z Garbage Collector (ZGC). It’s an area of memory tailored for short-lived objects.

The young generation is collected much more often than the old generation. This means that memory can be reclaimed quickly from objects no longer in use.

4.2. Optimized Concurrent Collection

The collection process in the newer generation uses concurrent marking and evacuation techniques. This dual approach allows ZGC to mark objects that are eligible to be recycled while the application is running.

Reducing repetitive interruptions to application threads lowers application latency and increases performance sensitivity.

4.3. Object Promotion and Dynamic Thresholds

After surviving several collection cycles in the young generation, objects can be promoted to the old generation. Such mechanisms segregate objects with short lifetimes from those likely to live longer. ZGC dynamically adjusts the lifetime threshold according to the observed object lifetime distribution.

The promotion threshold determines when an object should be promoted from the young to the old generation. This flexibility further helps optimize memory usage by promoting only those objects that require long-term storage.

4.4. Old Generation Collection

The old generation is collected less frequently than the young generation, as it primarily contains long-lived objects that have survived several garbage collection cycles. When a collection occurs in the old generation, it aims to reclaim memory from unused objects.

These objects may have remained in memory longer due to references from other application parts. Old-generation collections, similar to the young-generation ones, also use concurrent techniques. This ensures that application threads continue to function with minimal disruption as the garbage collector reclaims unused memory.

5. Visual Representation

To better understand the mechanics of Generational ZGC, let’s visualize the process.

generationalzgc

Objects are allocated in the Young Generation, which is collected often using concurrent marking and evacuation techniques. This frequent allocation means we can quickly reclaim memory from temporary objects, which helps maintain low latency and high performance.

Those objects that survive the collection multiple times are moved into the Old Generation, where memory is reclaimed less frequently. The algorithm automatically adjusts promotion thresholds based on object lifetime, optimizing memory management for long-lived objects.

6. Performance Implications for Temporary Objects

The introduction of Generational ZGC has significant implications for applications that create many temporary objects.

6.1. Reduced GC Overhead

Generational ZGC delivers high-performance gains to Java applications, especially in managing temporary objects. As a result, there’s less GC overhead.

By performing its work in the young generation, where most short-lived objects are located, Generational ZGC can quickly reclaim memory without performing expensive full heap collections. This leads to less time spent on garbage collection and more time used for actual application processing.

6.2. Improved Throughput

Another benefit of Generational ZGC is an improved throughput. Internal benchmarks have demonstrated a 10% improvement in throughput compared with its non-generational predecessor.

Applications that work with large amounts of data or that process a large number of concurrent operations stand to benefit most from this improvement. As a result,  applications can process more work in less time, often without requiring additional computing resources.

6.3. Lower Pause Times

Generational ZGC improves application responsiveness by approximately 10% -20% when it comes to P99 pause times. For applications where low latency or constant response time are critical success factors, this reduction in pause times is important.

This can be true for applications such as real-time trading systems for stock market arbitrage or latency-sensitive web services. In such cases, a stop-the-world collector that minimizes pause times is crucial for maintaining application responsiveness. This becomes especially important under heavy loads or during peak usage periods.

6.4. Efficient Memory Reclamation

Because Generational ZGC is more effective at recycling memory than any other GC method, it reduces the likelihood of out-of-memory errors under heavy allocation loads. By closely tracking the lifecycle of short-lived objects, the GC can promote them more aggressively.

This allows unused memory to be reclaimed faster, making space available for new allocations. This behavior is especially relevant for applications with high allocation rates that create numerous temporary objects. It helps keep the latency impact of GCs predictable and prevents crashes from memory exhaustion.

6.5. Summary

The table below summarizes the performance implications for temporary objects with Generational ZGC:

Aspect

Description

Impact

Reduced GC Overhead

Generational ZGC reclaims memory quickly by focusing on short-lived objects in the young generation

Less time spent on garbage collection, more application time

Improved Throughput

10% improvement in throughput compared to non-generational predecessors

Applications process more work in less time

Lower Pause Times

10-20% reduction in P99 pause times for low-latency applications

Improved application responsiveness, crucial for real-time apps

Efficient Memory Reclamation

Effective memory recycling reduces out-of-memory errors

Helps manage high allocation rates, preventing memory exhaustion

The table above shows the advantages of Generational ZGC for managing temporary objects in performance-sensitive applications.

7. Use Case: High-Frequency Trading with Real-Time Data

In high-frequency trading (HFT), speed is of paramount importance. Millions of market data points are processed per second, trades are generated, and the system performs rapid calculations. This processing provides temporary objects like trade orders and market snapshots that may only exist for a very short time.

7.1. How Generational ZGC Helps

Generational ZGC gathers these short-lived objects almost as soon as they’re created, helping to avoid memory overhead.

The ZGC engine works in the background without any pauses for trades. Even a microsecond pause in electronic transactions can mean missing out on profitable trades.

7.2. Boosting Throughput

With data coming in fast, the system must handle large volumes without slowing down. Generational ZGC clears temporary objects as quickly as they accumulate, ensuring memory is ready for the next round of calculations. Hence, the system keeps moving ahead even when the market gets busy.

7.3. Avoiding Memory Crash

Millions of objects are generated every second on high-frequency trading platforms. Memory management must be highly efficient to prevent the system from running out of memory and crashing.

ZGC constantly frees up space as short-lived objects are discarded.

7.4. Scaling with the Market

As trade volumes increase, the system must process more data without losing performance. The platform is easily scalable with generational ZGC, efficiently consuming memory, even as the workload doubles or triples.

8. Optimizing Applications for Generational ZGC

We should follow some best practices to leverage the full potential of Generational ZGC, especially in handling temporary objects.

8.1. Reevaluating Aggressive Object Pooling

Object pooling was used to be the most common optimization, where objects were reused instead of creating new ones to optimize memory and GC consumption. However, since Generational ZGC can work effectively for temporary objects, aggressive object pooling may no longer be necessary and may even cause performance issues.

8.2. Identifying Object Allocation Pattern

Profiling tools help identify parts of your code that generate excessive temporary objects. Visualizing this process aids in understanding and optimizing your application. We can use profiling tools such as JDK Mission Control, VisualVM, or JProfiler to gather real-time object allocation. We can visualize the sequence diagram for profiling:

object allocation patterns

The developer initiates profiling to monitor object allocations, prompting the profiler to instrument the application code and collect data. The developer then analyzes the report to identify hotspots and optimizes the code accordingly.

8.3. Tuning Heap Sizes for Generational ZGC

It’s important to consider heap size tuning. We can adjust the heap sizes to find the right balance between young and old generation sizes for our particular workload. The image below shows the flowchart for heap size tuning:

tuning heap
We start by running the application with the default heap sizes and monitor performance metrics to check for high temporary object allocation. If a high allocation is detected, we adjust the young generation size and re-run the application. We Continue this process, iteratively refining the heap sizes, until the optimal configuration is determined.

8.4. Monitoring and Tuning Garbage Collection

Continuous monitoring helps in fine-tuning GC behavior to improve application performance. Tools like JDK Flight Recorder (JFR) and JDK Mission Control (JMC) can help to monitor GC behavior and improve performance. We can visualize the sequence diagram for GC monitoring and tuning:

tuning garbage collection

The developer enables GC logging and starts the application, allowing garbage collection events to be logged and analyzed to identify performance issues. Based on this analysis, the developer adjusts the GC parameters and restarts the application to apply the optimizations.

9. Future Directions

As Generational ZGC matures, we can expect further optimizations and features:

  • Adaptive Generation Sizing – More sophisticated algorithms for dynamically adjusting the sizes of young and old generations based on application behavior
  • Enhanced Predictive Collection – Improved heuristics for predicting when to trigger collections in each generation
  • Integration with JIT Optimizations – Tightening cooperation between the GC and JIT compiler for better object allocation and management
  • Specialized Handling for Specific Object Types – Potential optimizations for common patterns of short-lived objects in Java applications

10. Conclusion

In this article, we have seen that Generational ZGC is an important innovation in JVM garbage collection technology. It builds on the generational hypothesis and collects garbage more effectively on short-lived objects. This translates into improved performance for a wide range of Java applications.

The gains in throughput, latency, and overall application performance are compelling reasons to consider Generational ZGC for modern Java deployments. This is especially true when there’s high temporal object allocation.