3.4 Fine-Tuning the Heap

In addition to the gross heap-tuning factors, a host of other parameters can be used for fine-tuning the VM heap. These other factors are usually strongly dependent on the garbage-collection algorithm being used by the VM, and the parameters vary for different VMs and different versions of VMs. In this section, I'll cover a few examples to give you an idea of the possibilities. Note that every VM and every version of the VM is different, and you need to retune the system with any change for this level of fine-tuning. Fine-tuning is probably worth doing only where every last microsecond is needed or for a really stable deployed system, i.e., one that needs no more development.

Note that the following sections refer to some of the internal heap areas used by the HotSpot generational garbage collector. Generally, the total VM heap consists of permanent space (Perm), old space (Old), young space (Young), and scratch or survivor space (Scratch). Parameters referring to "new" space (New), such as -XX:NewSize, refer to the combination Young+Scratch. The -Xmx parameter sizes Old+New. The full heap is Old+New+Perm.

3.4.1 Expanding the Heap

Most garbage-collection algorithms do not immediately expand the heap if they need space to create more objects, even when the heap has not yet been expanded to the maximum allowable size. Instead, there is usually a series of attempts to free up space by reclaiming objects, compacting objects, defragmenting the heap, and so on. If all these attempts are exhausted, then the heap is expanded. Several GC algorithms try to keep some free space available so that temporary objects can be created and reclaimed without expanding the heap. For example, the Sun 1.3 VM allows the parameter -XX:MinFreeHeapRatio=num, where num is 0 to 100, to specify that the heap should be expanded if less than num% of the heap is free. Similarly, the -XXMaxHeapFreeRatio parameter specifies when the heap should be contracted. The IBM VM uses -Xminf and -Xmaxf with decimal parameters between 0 and 1 (e.g., 20% is 20 for the Sun VM and 0.2 for the IBM VM). The Sun default is to try to keep the proportion of free space to living objects at each garbage collection within the 40%-70% range. That is, if less than 40% of the heap is free after a garbage collection (so more than 60% of the heap is full of objects), then the heap is expanded. Otherwise, the next garbage collection will likely occur sooner than desired. (IBM defaults are 0.3 min and 0.6 max.)

Once an application reaches a steady state, it has a fairly constant churn of objects (created and released). If the minimum free heap ratio is a small value (e.g., 10%), then there is not much space for objects to churn in, and garbage collection occurs frequently. The heap does not expand, because after each garbage collection the free heap ratio is achieved (e.g., 10% is filled, GC runs, and we have 10% available again so no heap expansion occurs). On the other hand, if the minimum free heap ratio is a large value (e.g., 60%), then GC runs much less frequently, but the pause when it runs is longer.

3.4.2 Minimizing Pauses

Garbage collection normally pauses the application. Pauses that last too long create bad user perceptions of performance. The primary technique to eliminate pauses is to identify which objects are being churned, causing the GC to activate, and try to minimize those objects. The secondary option is to reduce the heap size so that garbage collection runs more often, but for shorter periods. Incremental or "train" GC

As an alternative, -Xincgc changes the garbage-collection algorithm to use an incremental or "train" garbage-collection algorithm. The intention is to minimize individual pause time at the expense of overall GC time. The train algorithm clusters objects that reference each other and collects these clusters individually. The idea is to garbage-collect only a small fraction of the heap at a time, thus causing shorter pauses. But the algorithm is more costly in CPU time and results in longer total GC time. Concurrent GC

Another alternative is the concurrent garbage collector, -Xconcgc. The concurrent garbage collector tries to avoid stopping the application threads by asynchronously executing as much of the garbage collection algorithm as possible. Once again, this has a higher cost on the CPU, and total GC time is increased. The concurrent garbage collector should be especially effective on multiprocessor machines. Note that the garbage collector has always run in its own thread, but in order to access memory areas to run the garbage collection, it pauses application threads. It is this pausing that the concurrent garbage collector aims to minimize. Note that 1.4.1 provides parameters -XX:+UseParNewGC and -XX:+UseConcMarkSweepGC, which enable concurrent GC separately in the young and old spaces, respectively. Enlarge the "new" space

Finally, if you need a large heap and want to decrease pause times, you can try altering the size of the "new" space with generational garbage collectors (all Java 2 VMs). The "new" space (also called "Eden" or "the young generation") is the heap space where new objects are created. If the objects are short-lived, they are also garbage-collected rapidly in the new space. The longer pauses are usually caused by the garbage collections that run across the spaces outside the "new" space, i.e., garbage collection of the full heap, so the more objects that are churned in the new space, the better. If you need a large heap, try increasing the new space. For example, in 1.3 and 1.4 VMs, these parameters:

-Xms384m -Xmx384m -XX:NewSize=128m -XX:MaxNewSize=128m

set the full heap to 384 MB, of which one third is used for the new space, instead of SDK 1.3's default new space of 32 MB on Solaris or 2.5 MB on Windows. SDK 1.4 uses a default dependent on the heap size. Remember that new space is not one space, but internally consists of heap space plus scratch space (working areas for the algorithm). The option XX:SurvivorRatio= sets the ratio of sizes between scratch space and new space (Eden).

1.2 VMs use different parameters. For 1.2, the equivalent parameters would be:

-Xgenconfig:64m,64m,semispaces:256m,256m,markcompact -Xmx384m

which specify that the new space is a semispace collector of 64 MB, with 256 MB for the "old" space, using a mark-compact GC algorithm. (As you can see, fine-tuning the heap is complex, and changes with every VM.) Here's the complete list of 1.2 genconfig options:

 -Xgenconfig:<initial young size>,<max young size>,semispaces[,promoteall]:<initial 
old size>,<max old size> [,<collector>]

where collector can be incmarksweep (producing concurrent GC) or markcompact. The promoteall option forces the GC to move any object that survives a GC in young space to be immediately moved to old space; otherwise, the GC may leave the object in young space for longer. Since young-space garbage collection is faster, you might think that promoteall would decrease performance, but at least one in-depth test found that using promoteall improved performance (see http://wireless.java.sun.com/midp/articles/garbage/). In addition, an additional -bestFitFirst option seems to improve concurrent GC. The "best fit" refers to an internal free-list allocation policy that helps to reduce heap fragmentation (see http://dcb.sun.com/practices/devnotebook/gc_perspective.jsp).

3.4.3 Disabling System.gc( ) Calls

Prior to Java 2, explicit calls to System.gc( ) could assist an application. Garbage collection was pretty much an all-or-nothing affair, and often you knew better than the garbage collector when it was a good time to start garbage collecting. But with the introduction of generational garbage collection, explicit calls to System.gc( ) become disruptive, possibly forcing a full mark-sweep of the heap when the generational garbage collector was doing just fine. So Sun has added an option to disable the effect of calling System.gc( ) explicitly: -XX:+DisableExplicitGC.

3.4.4 Tuning RMI Garbage Collection

The frequency of distributed garbage collections can be set with the properties sun.rmi.dgc.client.gcInterval and sun.rmi.dgc.server.gcInterval. The default is one collection per minute (property values of 6000).

3.4.5 Extreme Heap and Intimate Shared Memory

The option -XX:+AggressiveHeap sets the heap to 3850 MB or more, allocates 256K to each thread, defers garbage collection as long as possible, and tries to run some GC activity in parallel (see the earlier discussion of concurrent GC options). This may or may not be a good tuning option; it is intended for very large servers. There is also an option to lock the heap in physical memory (see Section 14.3 and http://java.sun.com/docs/hotspot/ism.html for further details).

3.4.6 Loading a Huge Number of Classes

HotSpot stores as Java objects some of its own internal data structures, things like the internal representation of classes, methods, and fields. These are stored in a separate area called the Perm Space. If you are loading a truly huge number of classes, you may need to enlarge this space by using the -XX:MaxPermSize parameter.

3.4.7 Per-Thread Stack Size

Setting the JVM stack and native thread stack size (-oss <Java thread stack size>, -ss <native thread stack size>, -XX:ThreadStackSize=<thread stack size>) too large (e.g., greater than 2MB) can significantly degrade performance.

3.4.8 Eliminate Finalizers

Finalizers force objects to be promoted to old space and degrade the performance of the garbage collector. Finalizers postpone garbage collection until the finalizer is run, adding yet more overhead to the GC algorithm. There is no way to avoid this overhead apart from minimizing the use of finalization methods in the application.