3.3 Gross Tuning

The VMs provided by most vendors include the two main heap tuning parameters: -mx/-Xmx and -ms/-Xms. Respectively, these parameters set the maximum and starting sizes of the heap in bytes. They are typically available with every VM.

VMs vary as to whether they accept the -mx and -ms parameters or the -Xmx and -Xms parameters, or both. They also vary about accepting a space between the number following the parameter and accepting shorthand notations of K and M for kilobytes and megabytes, e.g., -Xmx32M. Check the documentation or simply try the various possibilities for your VM).

Tuning the heap with these two parameters requires trial and error, but is relatively simple. You don't need to consider the exact garbage-collection algorithm or how different parameters might affect each other. Instead, you can identify the cost of garbage collection to the application using the measurement techniques covered in Chapter 2. You can then simply alter the two parameters and remeasure using the same technique. Typically, you might want to use a range of values for the maximum heap size, keeping the starting heap size either absolutely constant (e.g., 1 megabyte) or relatively constant (e.g., half the maximum heap), and graph the result, looking for where garbage collection has the minimum cost.

Note that GC activity can take hours to settle into a regular pattern. If you are tuning a long-lived application, bear this in mind when looking at the GC output.

Gross heap tuning is fairly stable, in that moving to a different VM or tweaking the application usually won't invalidate the tuning parameters. They may no longer be the most optimal sizes after such changes, but they should still be reasonable. The following sections describe some considerations for heap parameters.

3.3.1 Problems with a Larger Heap

The heap size should not become so large that physical memory is swamped and the system has to start paging. So keep the maximum heap size below the size of the physical memory (RAM) on the machine. Also, subtract from the RAM the amount of memory required for other processes that will be running at the same time as the Java application, and keep the maximum heap size below that value.

A larger heap allows more objects to exist in memory before garbage collection is required to reclaim space for new objects. However, a larger heap also makes the garbage collection last longer, as it needs to work through more objects. In the absence of concurrent garbage collection (see the information about -Xconcgc in Section 3.4), a larger heap causes longer individual perceptible pauses, which may be undesirable. You need to balance the pause times against the overall garbage-collection cost. (Using -Xincgc is an alternative that is also described in the section on fine tuning.)

3.3.2 Starting Versus Maximum Heap Size

There are many different suggestions about what the starting heap should be compared to the maximum. The most frequent suggestions include:

  • Set the starting heap size the same as the maximum heap size.

  • Set the starting heap size to the size needed by the maximum number of live objects (estimated or measured in trials), and set the maximum heap size to about four times this amount.

  • Set the starting heap size to half the maximum heap size.

  • Set the starting heap size between 1/10 and 1/4 the maximum heap size.

  • Use the default initial heap size (1 megabyte).

Although there is no conclusive evidence that any of these suggestions represents the best approach in all situations, each has been shown to be appropriate in different applications. Here are some rationales. Assuming you've worked out what the maximum heap size should be, then growing the JVM memory can be considered as pure overhead, requiring multiple system calls and resulting in segmented system memory allocation. If you figure that you are going to get to the maximum heap anyway, then there is a good argument for simply starting out at the maximum heap (the first suggestion), thus avoiding the growth overhead as well as getting a heap that is less segmented. However, this can mean longer pauses when garbage collection kicks in, so the system load might not be smoothed out as much as you'd want. But a generational garbage collector will not necessarily suffer from this longer pause problem, as it specifically smooths out the GC load.

An alternative view is that there is this lovely garbage-collection system in the JVM, which will grow the JVM to be just as big as needed and no more, so why not let it do its job? This way, despite the overhead in growing the JVM, you will end up using the minimum resources and the GC should be optimizing what it does best, i.e., handling and maintaining memory. With this argument, you set the starting heap to 1MB (the last suggestion) and the maximum as high as reasonable.

A combination of these two rationales might lead you to one of the intermediate recommendations. For example, assuming that the maximum heap is an overestimate of the ultimate JVM size, then half the maximum could be a good starting point to minimize memory allocation and memory segmentation overhead while still giving the GC space to optimize memory usage.

3.3.3 Benchmarking Considerations

When running benchmarks, some engineers try to manipulate the benchmark and heap size so that no garbage collection needs to occur during the run of the benchmark. This is an idealized situation, but it may be appropriate if your application is expected to run for only a short period. In any case, be aware that this may apply to benchmarks presented to you.

The -noclassgc/-Xnoclassgc options prevent classes from being garbage-collected. If you are loading classes indirectly (e.g., through Class.forName( ) or by J2EE automatic classloading), then classes can be repeatedly garbage-collected and reloaded. Reloaded classes are also reinitialized, so use the -noclassgc/-Xnoclassgc parameter to prevent them from being garbage-collected.