days
0
-38
-2
hours
0
-1
minutes
-2
-1
seconds
-1
-1
search
Adaptive garbage collection

Simple & effective G1 GC tuning tips

Ram Lakshmanan
garbage collection
© Shutterstock / Lorna Roberts

In this article, Ram Lakshmanan shares a few tips to tune G1 Garbage collector to obtain optimal performance that are simple, yet effective. Tune your application to get optimal performance with these tips, methods, and explanations. G1 has been the default GC algorithm since Java 9.

G1 GC is an adaptive garbage collection algorithm that has become the default GC algorithm since Java 9. We would like to share a few tips to tune G1 Garbage collector to obtain optimal performance.

1. Maximum GC Pause time

Consider passing ‘-XX:MaxGCPauseMillis’ argument with your preferred pause time goal. This argument sets a target value for maximum pause time. G1 GC algorithm tries it’s best to reach this goal.

SEE ALSO: Garbage Collection tuning & troubleshooting crash course

2. Avoid setting young gen size

Avoid setting the young generation size to a particular size (by passing ‘-Xmn, -XX:NewRatio’ arguments). G1 GC algorithm modifies young generation size at runtime to meet its pause-time goals. If the young generation size is explicitly configured, then pause time goals will not be achieved.

3. Remove old arguments

When you are moving from other GC algorithms (CMS, Parallel, …) to G1 GC algorithm, remove all the JVM arguments related to the old GC algorithm. Typically passing old GC algorithms arguments to G1 will have no effect, or it can even respond in a negative way.

4. Eliminating String duplicates

Because of inefficient programming practices, modern applications waste a lot of memory. Here is a case study showing memory wasted by the Spring Boot framework. One of the primary reasons for memory wastage is the duplication of string. A recent study indicates that 13.5% of application’s memory contains duplicate strings. G1 GC provides an option to eliminate duplicate strings when you pass the ‘-XX:+UseStringDeduplication’ argument. You may consider passing this argument to your application if you are running on Java 8 update 20 and above. It has the potential to improve the overall application’s performance. You can learn more about this property in this article.

5. Understand default settings

For tuning purposes, in the below table, we have summarized important G1 GC algorithm arguments and their default values:

garbage collection

6. Study GC Causes

One of the effective ways to optimize G1 GC performance is to study the causes triggering the GC and provide solutions to reduce them. Here are the steps to study the GC causes.

1. Enable GC log in your application. It can be enabled by passing following JVM arguments to your application during startup time:

Up to Java 8:


-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:{file-path}

From Java 9 and above:


-Xlog:gc*:file={file-path}

{file-path}: is the location where GC log file will be written.

2. You can analyze the GC log file using free tools like GCeasy, Garbage Cat, HP Jmeter. These tools report the reasons that are triggering the GC activity. Below is the GC Causes table generated by GCeasy tool when G1 GC Log file was uploaded. A full analysis report can be found here.

garbage collection

Fig: G1 GC causes (excerpt from GCeasy report)

Below are the solutions to address each of them.

6.1. Full GC – Allocation Failure

Full GC – Allocation failures happen for two reasons:

  • Application is creating too many objects that can’t be reclaimed quickly enough.
  • When heap is fragmented, direct allocations in the Old generation may fail even when there is a lot of free space.

Here are the potential solutions to address this problem:

  1. Increase the number of concurrent marking threads by setting ‘-XX:ConcGCThreads’ value. Increasing the concurrent marking threads will make garbage collection run fast.
  2. Force G1 to start marking phase earlier. This can be achieved by lowering ‘-XX:InitiatingHeapOccupancyPercent’ value. The default value is 45. It means the G1 GC marking phase will begin only when heap usage reaches 45%. By lowering the value, the G1 GC marking phase will get triggered earlier so that Full GC can be avoided.
  3. Even though there is enough space in a heap, a Full GC can also occur due to lack of a contiguous set of space. This can happen because of a lot of humongous objects present in the memory (refer to section ‘6.3. G1 Humongous Allocation’ in this article). A potential solution to solve this problem is to increase the heap region size by using the option ‘-XX:G1HeapRegionSize’ to decrease the amount of memory wasted by humongous objects.

6.2. G1 Evacuation Pause or Evacuation Failure

When you see G1 Evacuation pause, then G1 GC does not have enough memory for either survivor or promoted objects or both. The Java heap cannot expand since it is already at its max. Below are the potential solutions to fix this problem:

  1. Increase the value of the ‘-XX:G1ReservePercent’ argument. Default value is 10%. It means the G1 garbage collector will try to keep 10% of memory free always. When you try to increase this value, GC will be triggered earlier, preventing the Evacuation pauses.
  2. Start the marking cycle earlier by reducing the ‘-XX:InitiatingHeapOccupancyPercent’. The default value is 45. Reducing the value will start the marking cycle earlier. GC marking cycles are triggered when heap’s usage goes beyond 45%. On the other hand, if the marking cycle is starting early and not reclaiming, increase the ‘-XX:InitiatingHeapOccupancyPercent’ threshold above the default value.
  3. You can also increase the value of the ‘-XX:ConcGCThreads’ argument to increase the number of parallel marking threads. Increasing the concurrent marking threads will make garbage collection run fast.
  4. If the problem persists you may consider increasing JVM heap size (i.e. -Xmx)

6.3. G1 Humongous Allocation

Any object that is more than half a region size is considered a “Humongous object”. If the regions contain humongous objects, space between the last humongous object in the region and the end of the region will be unused. If there are multiple such humongous objects, this unused space can cause the heap to become fragmented. Heap fragmentation is detrimental to application performance. If you see several Humongous allocations, please increase your ‘-XX:G1HeapRegionSize’. The value will be a power of 2 and can range from 1MB to 32MB.

6.4. System.gc()

When ‘System.gc()’ or ‘Runtime.getRuntime().gc()’ API calls are invoked from your application, stop-the-world Full GC events will be triggered. You can fix this problem through the following solutions:

a. Search & Replace

This might be a traditional method 😊, but it works. Search in your application code base for ‘System.gc()’ and ‘Runtime.getRuntime().gc()’. If you see a match, then remove it. This solution will work if ‘System.gc()’ is invoked from your application source code. If ‘System.gc()’ is going to be invoked from your 3rd party libraries, frameworks, or through external sources then this solution will not work. In such circumstances, you can consider using the option outlined in #b and #c.

b. -XX:+DisableExplicitGC

You can forcefully disable System.gc() calls by passing the JVM argument –‘XX:+DisableExplicitGC’ when you launch the application. This option will silence all the ‘System.gc()’ calls that are invoked from your application stack.

c. -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses

You can pass ‘-XX:+ExplicitGCInvokesConcurrent’ JVM argument. When this argument is passed, GC collections run concurrently along with application threads to reduce the lengthy pause time.

SEE ALSO: Garbage collection log analysis complements APM tools

d. RMI

If your application is using RMI, you can control the frequency in which ‘System.gc()’ calls are made. This frequency can be configured using the following JVM arguments when you launch the application:


-Dsun.rmi.dgc.server.gcInterval=n
-Dsun.rmi.dgc.client.gcInterval=n

The default value for these properties in:

JDK 1.4.2 and 5.0 is 60000 milliseconds (i.e. 60 seconds)

JDK 6 and later release is 3600000 milliseconds (i.e. 60 minutes)

You might want to set these properties to a very high value so that it can minimize the impact.

For more details about ‘System.gc()’ calls, and it’s GC impact you can refer to this article.

6.5 Heap Dump Initiated GC

‘Heap dump Initiated GC’ indicates that heap dump was captured from the application using tools like jcmd, jmap, profilers,… Before capturing the heap dump, these tools typically trigger full GC, which will cause long pauses.. We shouldn’t be capturing heap dumps unless there is an absolute necessity.

Conclusion

We hope you find this article useful. Best wishes to tune your application to get optimal performance.

Author
Ram Lakshmanan
Every single day, millions & millions of people in North America—bank, travel, and commerce—use the applications that Ram Lakshmanan has architected. Ram is an acclaimed speaker in major conferences on scalability, availability, and performance topics. Recently, he has founded a startup, which specializes in troubleshooting performance problems.

guest
0 Comments
Inline Feedbacks
View all comments