Tuning Java Garbage Collection

The Java virtual machine automatically handles garbage collection of objects that are no longer referenced, and you would normally not have to change or tweak the default garbage collection settings. That is, unless you are dealing with a long-running application (e.g. a web application), or if performance is of great importance. For those cases, i have found a few basic steps which provide a good starting point in tweaking those settings.

In order to tune the garbage collector, you would first have to know how it is currently behaving and performing. This can be achieved enabling detailed garbage collection logging, and then monitoring the log file. You can get a useful garbage collection log output by appending the options

-verbose:gc -Xloggc:gc.log -XX:+PrintGCTimeStamps -XX:+PrintGCDetails

to the java command when starting up your Java application. So your application start up command would end up looking something like:

java -classpath some/path:some_jarfile.jar -verbose:gc -Xloggc:gc.log -XX:+PrintGCTimeStamps -XX:+PrintGCDetails your.main.Application

After your application has run for some time, you would see lines like these in the garbage collection log file (gc.log in the example):

3779.257: [GC 3779.257: [DefNew: 308160K->9280K(308160K), 0.3048983 secs] 700726K->425258K(2087872K), 0.3050826 secs]


3788.365: [Full GC 3788.366: [Tenured: 415978K->237898K(1779712K), 3.5419520 secs] 689157K->237898K(2087872K), [Perm : 81663K->81663K(81664K)], 3.5421538 secs]

A very basic explanation of those two lines are as follows:

Line 1: A minor collection took place at time T + 3779.259 seconds (3779.259 seconds after the application was started up). Memory usage in the young space was brought down from 308,160 KB to 9,280 KB while on the whole, 275,468 KB was recovered from the Java heap (from 700,726 KB to 425,258 KB). The collection took 0.3050826 seconds.

Line 2: A full collection took place at time T + 3788.365 seconds. 178,080 KB was recovered from the tenured space, 451,259 KB from the Java heap overall. The collection took 3.5421538 seconds.

Now, with that logging in place, it is possible to spot a few basic performance-related issues with memory and garbage collection.

Memory Leaks

If the figure representing the total used memory after collection (e.g. 425,258 KB in the first case) continuously increases for a relatively long amount of time, a memory leak situation may be occurring. Profiling the application for a period of time and monitoring objects which increase endlessly in count and size could help tremendously in hunting down the source of the leak. From my personal experience, memory leaks are most commonly caused by continuously adding objects to a static or long living collection (e.g. a cache) and neglecting to remove them.

Barely Sufficient Heap Size

If the figure representing the total used memory after collection (e.g. 425,258 KB in the first case) is close to the total available heap size (e.g. 2,087,872 KB in the first case) before a significant amount of time has even passed, the set heap size may be insufficient and may need to be increased. If heap usage continues to increase with time, trashing may eventually take place (i.e. the application spends almost all its time doing only garbage collection), with the occasional java.lang.OutOfMemoryError causing havoc. Even in the case where memory usage is already stable, application performance may still benefit from increasing the heap size, as it would then perform garbage collection at less regular intervals.

Young Generation Guarantee

Typically, most of the collections occurring should by minor collections (first line in example above). If full collections (second line) happen for the majority of the time, memory usage due to long living objects may too high and the young generation guarantee can never be met. One possible solution for this would be to set the size of the new space to a smaller number (thus allowing more space in the tenured generation). This is likely to happen in applications which rely heavily on caching (for performance, somewhat ironically), and the symptoms can be easily spotted from the garbage collection logs. A simple tweak in the new size setting usually results in significant performance improvements.

Garbage Collection Strategy

Depending on factors such as the physical configuration of your server (e.g. number of processors), the type of application (e.g. online transaction or batch processing), and the memory usage pattern (e.g. heavy caching), it may even be worthwhile to explore alternative garbage collection strategies. In one of my previous projects, an online transaction application running on a server with multiple processors, we experienced a significant performance boost by switching from the default strategy to the concurrent low pause collector with parallel minor collections.

Reference: http://java.sun.com/docs/hotspot/gc1.4.2/