Java Parallel GC Tuning
Published: 2023-09-15 13:49:11

Parallel garbage collector (Parallel GC) is one of the oldest Garbage Collection algorithms introduced in JVM to leverage the processing power of modern multi-core systems. Parallel GC aims to reduce the impact of GC pauses by utilizing multiple threads to perform garbage collection in parallel. In this article, we will delve into the realm of Parallel GC tuning specifically. However, if you want to learn more basics of Garbage Collection tuning, you may watch this JAX London conference talk.

**When to use Parallel GC?**
You can consider using Parallel GC for your application, if you have any one of the requirement:

Throughput Emphasis: If your application has high transactional throughput requirements and can tolerate long, occasional pauses for garbage collection, Parallel GC can be a suitable choice. It focuses on maximizing throughput by allowing garbage collection to occur concurrently with application execution.
Batch Processing: Applications that involve batch processing or data analysis tasks can benefit from Parallel GC. These types of applications often perform extensive computations, and Parallel GC helps minimize the impact of garbage collection on overall processing time.
Heap Size Considerations: Parallel GC is well-suited for applications with moderate to large heap sizes. If your application requires a substantial heap to accommodate its memory needs, Parallel GC can efficiently manage memory and reduce the impact of garbage collection pauses.

**How to enable Parallel GC?**
To explicitly configure your application to use Parallel GC, you can pass the following argument when launching your Java application:

-XX:+UseParallelGC
This JVM argument instructs the JVM to use the Parallel GC algorithm for garbage collection. However please note that if you don’t explicitly specify a garbage collection algorithm, in all the server class JVMs until Java 8, the default garbage collector is set to Parallel GC.

Most used Parallel GC JVM arguments
In the realm of Java Parallel GC tuning, there are few key JVM arguments that provide control over crucial aspects of the garbage collection process. We have grouped those JVM arguments in to 3 buckets:
a. Heap and Generation Size parameters

b. Goal-Based Tuning parameters

c. Miscellaneous parameters

Let’s get on to the details:

**a. Heap and Generation Size parameters**
Garbage collection (GC) tuning for the Parallel Collector involves achieving a delicate balance between the size of the entire heap and the sizes of the Young and Old Generations. While a larger heap generally improves throughput, it also leads to longer pauses during GC. Consequently, finding the optimal size for the heap and generations becomes crucial. In this section, we will explore key JVM arguments that enable the adjustment of heap size and generation sizes to achieve an efficient GC configuration.

-Xmx: This argument sets the maximum heap size, which establishes the upper limit for memory allocation. By carefully selecting an appropriate value for -Xmx, developers can control the overall heap size to strike a balance between memory availability and GC performance.

-XX:NewSize and -XX:MaxNewSize or -XX:NewRatio: These arguments govern the size of the Young Generation, where new objects are allocated. -XX:NewSize sets the initial size, while -XX:MaxNewSize or -XX:NewRatio control the upper limit or the ratio between the young and tenured generations, respectively. Adjusting these values allows for fine-tuning the size and proportion of the Young Generation. Here is a success story of a massive technology company which reduced their young generation size and saw significant improvement to its overall application’s response time.

-XX:YoungGenerationSizeIncrement and -XX:TenuredGenerationSizeIncrement: These arguments define the size increments for the Young and Tenured Generations, respectively. The size increments of the young and tenured generations are crucial factors in memory allocation and garbage collection behavior. Growing and shrinking are done at different rates. By default a generation grows in increments of 20% and shrinks in increments of 5%. The percentage for growing is controlled by the command-line option -XX:YoungGenerationSizeIncrement=<Y> for the young generation and -XX:TenuredGenerationSizeIncrement=<T> for the tenured generation.

-XX:AdaptiveSizeDecrementScaleFactor: This argument determines the scale factor used when decrementing generation sizes during shrinking. The percentage by which a generation shrinks is adjusted by the command-line flag -XX:AdaptiveSizeDecrementScaleFactor=<D>. If the growth increment is X percent, then the decrement for shrinking is X/D percent.

**b. Goal-Based Tuning Parameters**
To achieve optimal performance in garbage collection, it is crucial to control GC pause times and optimize the GC throughput, which represents the amount of time dedicated to garbage collection compared to application execution. In this section, we will explore key JVM arguments that facilitate goal-based tuning, enabling developers to fine-tune these aspects of garbage collection.

-XX:MaxGCPauseMillis: This argument enables developers to specify the desired maximum pause time for garbage collection in milliseconds. By setting an appropriate value, developers can regulate the duration of GC pauses, ensuring they stay within acceptable limits.

-XX:GCTimeRatio: This argument sets the ratio of garbage collection time to application time using the formula 1 / (1 + N), where N is a positive integer value. The purpose of this parameter is to define the desired allocation of time for garbage collection compared to application execution time, for optimizing the GC throughput. For example, let’s consider the scenario where -XX:GCTimeRatio=19. Using the formula, the goal is to allocate 1/20th or 5% of the total time to garbage collection. This means that for every 20 units of time (e.g., milliseconds) of combined garbage collection and application execution, approximately 1 unit of time will be allocated to garbage collection, while the remaining 19 units will be dedicated to application execution. The default value is 99, which sets a goal of 1% of the time for garbage collection.

-XX:GCTimePercentage: This argument allows developers to directly specify the desired percentage of time allocated to garbage collection in relation to application execution time (i.e., GC Throughput). For instance, setting ‘-XX:GCTimePercentage=5’ represents a goal of allocating 5% of the total time to garbage collection, with the remaining 95% dedicated to application execution. 

Note: Developers can choose to use either ‘-XX:GCTimeRatio‘ or ‘-XX:GCTimePercentage‘ as alternatives to each other. Both options provide flexibility in expressing the desired allocation of time for garbage collection. I would prefer using ‘-XX:GCTimePercentage’ over ‘-XX:GCTimeRatio’ because of its ease of understanding.

**c. Miscellaneous Parameters**
In addition to the previously discussed JVM arguments, there are a few other parameters that can be useful for tuning the Parallel GC algorithm. Let’s explore them.

-XX:ParallelGCThreads: This argument allows developers to specify the number of threads used for garbage collection in the Parallel GC algorithm. By setting an appropriate value based on the available CPU cores, developers can optimize throughput by leveraging the processing power of multi-core systems. It’s important to strike a balance by avoiding too few or too many threads, as both scenarios can lead to suboptimal performance.

-XX:-UseAdaptiveSizePolicy: By default, the ‘UseAdaptiveSizePolicy’ option is enabled, which allows for dynamic resizing of the young and old generations based on the application’s behavior and memory demands. However, this dynamic resizing can lead to frequent “Full GC – Ergonomics” garbage collections and increased GC pause times. To mitigate this, we can pass the -XX:-UseAdaptiveSizePolicy argument to disable the resizing and reduce GC pause times. Here is a real-world example and discussion around this JVM argument.

**Tuning Parallel GC behavior**
Studying the performance characteristics of Parallel GC is best achieved by analyzing the GC log. The GC log contains detailed information about garbage collection events, memory usage, and other relevant metrics. There are several tools available that can assist in analyzing the GC log, such as GCeasy, IBM GC & Memory visualizer, HP Jmeter, Google Garbage cat. By using these tools, you can visualize memory allocation patterns, identify potential bottlenecks, and assess the efficiency of garbage collection. This allows for informed decision-making when fine-tuning Parallel GC for optimal performance. 

**Conclusion**
In conclusion, optimizing the Parallel GC algorithm through fine-tuning of JVM arguments and studying its behavior enables developers to achieve efficient garbage collection and improved performance in Java applications. By adjusting parameters such as heap size, generation sizes, and goal-based tuning parameters, developers can optimize the garbage collection process. Continuous monitoring and adjustment based on specific requirements are essential for maintaining optimal performance. With optimized Parallel GC tuning, developers can maximize memory management, minimize GC pauses, and unlock the full potential of their Java applications.