Skip to main content

Fine-tuning Java garbage collection performance

How to detect and troubleshoot garbage collection issues with the IBM Java Virtual Machine

Sumit Chawla (sumitc@us.ibm.com), Technical Lead, eServer Java Enablement, IBM
Sumit Chawla works for IBM's eServer division, providing Java support to ISVs. You can contact him at sumitc@us.ibm.com.

Summary:  Is your Java-based application fully using the capabilities of the IBM eServer hardware it's running on? In this article, the author shows how to find out whether garbage collection, the task carried out by Java Virtual Machine in the background to reclaim unusable space, is finely tuned. He then provides several recommendations to address your garbage collection issues.

Date:  01 Jan 2003
Level:  Introductory
Activity:  11733 views

Introduction

The garbage collection implementation is key to the superior performance of the IBM Java Virtual Machine (JVM). While most other JVMs need a lot of tweaking to provide optimal performance, IBM JVMs are designed to work well in most scenarios with their "out of the box" settings. In certain situations, however, garbage collection performance nosedives for no apparent reason. The effects can be an unresponsive server, a frozen screen, or a complete failure, often with cryptic messages such as "Totally out of heap space." Luckily, in almost all such situations, the cause is easy to locate and usually quite easy to correct.

This article shows how to locate potential causes of performance degradation. Since garbage collection is such a vast and complex topic, this article builds on an excellent set of articles already published (see Resources).Though most of the suggestions discussed in this article treat your Java program as a black-box, there also are some ideas that you can put in use while designing or coding to avoid potential problems altogether.

The information in this article can be used on all IBM eServer platforms, except for resettable JVM configurations (see Resources). The trace examples, unless otherwise noted, were taken from Java 1.3.1 build ca131-20020706 on a 4-way box running AIX 5.1.


Heap management overview

The JVM allocates the heap during initialization. The size of the heap varies, based on the specified or default heap minimum and maximum sizes, and the usage of the heap. It might help to visualize the heap as shown in Figure 1 below, which shows heapbase, heaplimit, and heaptop.


Figure 1. A conceptual view of the heap
A conceptual view of the heap

Heapbase marks the base of the heap, while heaptop is the absolute maximum the heap can grow to. The difference, heaptop - heapbase, is decided by the command-line parameter -Xmx. This and other command-line parameters are documented in the developerWorks article on verbosegc and command-line parameters. The heaplimit pointer can go up, when the heap can expand, or down, as the heap shrinks. The heaplimit can never exceed heaptop, and can never go below the initial heap size specified using -Xms. The size of the heap at any time is heaplimit - heapbase.

The heap is expanded when the ratio of free heap to total heap falls below the value specified by -Xminf (minf is the minimum free). It gets shrunk if the ratio of free heap to total heap exceeds the value specified by -Xmaxf (maxf is the maximum free). The defaults for -Xminf and -Xmaxf are 0.3 and 0.6, respectively, so the JVM tries to maintain a heap that is between 30 and 60 percent free at all times. The parameters -Xmine (mine is the minimum expansion size) and -Xmaxe (maxe is the maximum expansion size) control the amount of expansion of the heap. These four parameters have no effect on fixed-size heaps (JVM started with -Xms value equal to -Xmx value, implying HeapLimit = HeapTop), because fixed-size heaps do not expand or shrink.

When a Java thread makes a request for a piece of storage, and the JVM is unable to locate a large enough chunk of storage to satisfy the request, an allocation failure (AF) is said to have occurred. This is when garbage collection becomes unavoidable. Garbage collection involves collecting all the "unreachable" references so that the space consumed by them can be reused. It is carried out by the thread that makes an allocation request, and is a Stop-The-World (STW) mechanism; all other threads of the Java application are suspended while garbage collection is going on (except the garbage collection helper threads).

IBM implementation uses a garbage collection algorithm called mark-sweep-compact (MSC), which is named after three distinct phases.

Mark
All the objects that are "reachable," or alive, are marked. This phase starts off by locating "roots," such as objects on thread stacks, Java Native Interface (JNI) local and global references, and so on, and following each reference recursively until all references are marked.
Sweep
All the objects that were allocated but are not marked anymore are swept away, and the space used by them is reclaimed.
Compact
Holes or fragments in the heap are removed by moving the live objects together in the heap.

See Resources for more details on the MSC algorithm.

Parallel vs. concurrent

Though the garbage collection itself is STW, all the recent releases of IBM JVM use multiple "helper" threads on multiprocessor machines to help reduce the amount of time spent in a phase. Hence, JVM 1.3.0 has the mark phase being run in parallel mode by default. JVM 1.3.1 has a parallel mode for both the mark and sweep phases, and has an optional concurrent mark phase that can be switched on using the command line switch -Xgcpolicy:optavgpause. JVM 1.4.0, the latest version available when writing this article, has an incremental compaction mode, which parallelizes the compaction phase. It is important to understand the difference between parallel and concurrent when discussing these modes.

On a multiprocessor system with N CPUs, a JVM supporting parallel mode starts N-1 garbage collection helper threads at the time of initialization. These threads remain idle at all times when the application code is running; they are called into play only when garbage collection is active. For a particular phase, work is divided between the thread driving the garbage collection and the helper threads, making a total of N threads running in parallel on an N-CPU machine. The only way to disable the parallel mode is to use the -Xgcthreads parameter to change the number of garbage collection helper threads being started.

When using concurrent mode, a background thread is started by the JVM (independently from the garbage collection helper threads), and some part of the work is done in the background while the application threads are active. The background thread tries to complete part of the garbage collection work concurrently with the application, so that when garbage collection does kick in, the resulting STW pause will be reduced. In some cases, though, concurrent processing can have a negative impact on performance, especially for CPU-intensive applications.

The following table shows the type of processing for each stage of garbage collection, by the JVM version.

 MarkSweepCompact
IBM JVM 1.2.2XXX
IBM JVM 1.3.0PXX
IBM JVM 1.3.1P, CPX
IBM JVM 1.4.0P, CPP

Where:

X
Single-threaded operation
P
Parallel operation (all helper threads work during the garbage collection cycle)
C
Concurrent operation (Background thread runs concurrently with application threads)


Analyzing verbosegc output

Though there are profilers and other third-party tools available, this article only discusses analyzing verbosegc logs. These logs, which are produced by the JVM when the -verbosegc command-line parameter is used, form quite a reliable platform-independent debugging tool. The complete verbosegc syntax is explained in the article verbosegc and command-line parameters.

Enabling verbosegc can have a performance impact on an application. If such impact is unacceptable, a test system should be used to collect the verbosegc logs. It is common for server applications to keep verbosegc enabled at all times. This can be a good way to monitor the overall health of the JVM, and can be invaluable in post-mortem if an OutOfMemory error occurs.

To analyze verbosegc traces effectively, it is important to concentrate on the relevant information and filter out the "noise." It's not hard to write scripts for extracting information from long verbosegc traces, but the format of these traces can (and usually does) change from one JVM release to another. The trace examples below indicate the important information in a bold, or blue, font. It is quite straightforward to locate this information in verbosegc logs, even if the trace formats seem to differ significantly.

Are you refreshed?

I strongly recommend that you upgrade to the latest available service refresh (SR) of the JVM before you try any of the suggestions in the rest of this article. A lot of fixes and enhancements go into each successive service refresh, so a move to a new service refresh benefits both performance and stability of the JVM. A migration to the latest available version (such as JVM 1.4.0 or 1.3.1, based on the platform) provides access to enhanced performance features. Be sure that all prerequisite OS patches (for example, maintenance levels on AIX) are installed for the JVM. This information is documented in the readme file that accompanies the SDK/JRE.

Getting the heap size right

Calculating the right values for heap size parameters is quite easy, but it can have a dramatic effect on the application startup time and runtime performance. The startup and maximum sizes, controlled through the parameters -Xms and -Xmx respectively, are usually set based on estimates of heap size consumption under ideal and heavy loads, but verbosegc can help take out the guesswork in determining these values. This is shown in the verbosegc output for an application, from the time it was launched to the time that it finished its initialization (or became "Ready"), as follows.


			
<GC[0]: Expanded System Heap by 65536 bytes
<GC[0]: Expanded System Heap by 65536 bytes

<AF[1]: Allocation Failure. need 64 bytes, 0 ms since last AF>
<AF[1]: managing allocation failure, action=1 (0/3983128) (209640/209640)>
<GC(1): GC cycle started Tue Oct 29 11:05:04 2002
<GC(1): freed 1244912 bytes, 34% free (1454552/4192768), in 10 ms>
<GC(1): mark: 9 ms, sweep: 1 ms, compact: 0 ms>
<GC(1): refs: soft 0 (age >= 32), weak 5, final 237, phantom 0>
<AF[1]: completed in 12 ms>

The above trace shows there was 0% heap free when the first AF occurred (0 bytes available out of 3983128 bytes). Also, after the first garbage collection, the free percentage rose to 34%, which is barely above the -Xminf mark (default is 30%). Depending on the application usage, it might be a good idea to allocate a larger initial heap, using -Xms. In the example application above, the next AF will almost certainly result in a heap expansion. A larger initial heap will avoid such a scenario. Once the application can reach the Ready state without facing any AF in a normal scenario, a good value for the startup heap size has been determined. Similarly, the maximum heap size can be ascertained by stressing the application and finding the value for -Xmx that avoids an OutOfMemory error.

If the heap size is too small, there will be frequent garbage collection cycles, even if the application does not hold onto many objects for a long time. So, a natural tendency may be to go for a very large heap. But there are physical limits on the maximum size of the heap, based on platform and other restrictions. If the heap gets paged, the performance will deteriorate significantly, so the heap size must never exceed the amount of physical memory installed on the system. For example, if 1 GB of RAM is installed on an AIX box, a heap of 2 GB should not be allocated for the Java application.

Even if the application is running on a p690 monster with 64 GB of RAM, it doesn't make -Xmx60g (with 64-bit JVM, of course!) acceptable in all cases. Though the application might not encounter an AF for a long time, the pause due to STW will be quite nasty when an AF does eventually occur. The traces below, taken from 64-bit JVM 1.3.1 (build caix64131-20021102) with a 20 GB heap on a 32 GB AIX system, show this effect of very large heaps.

<AF[29]: Allocation Failure. need 2321688 bytes, 88925 ms since last AF>
<AF[29]: managing allocation failure, action=1 (3235443800/20968372736) 
   (3145728/3145728)>
<GC(29): GC cycle started Mon Nov 4 14:46:20 2002
<GC(29): freed 8838057824 bytes, 57% free (12076647352/20971518464), 
   in 4749 ms>
<GC(29): mark: 4240 ms, sweep: 509 ms, compact: 0 ms>
<GC(29): refs: soft 0 (age >= 32), weak 0, final 1, phantom 0>
<AF[29]: completed in 4763 ms>

Almost five seconds for a garbage collection, and that was without compaction! The time to complete a garbage collection cycle grows directly proportional to the size of the heap. A good rule is to keep the heap size as large as needed, but no more.

A common performance-tuning measure is to make the initial heap size (-Xms) equal to the maximum heap size (-Xmx). Since no heap expansion or contraction occurs, this can result in significant performance gains in some situations. Usually, only the applications that need to handle a surge of allocation requests keep a substantial difference between initial and maximum heap sizes. Remember, though, that if -Xms100m -Xmx100m is specified, the JVM will consume 100 Megabytes of memory for its complete lifetime, even if the heap usage never exceeds 10%.

As an aside, it is possible to use -Xinitsh to allocate a larger system heap right at the start, to remove the Expanded System Heap messages. But these messages can be ignored safely. The system heap is expanded as needed and is never garbage collected; it contains only the objects that last through the lifetime of the JVM instance.

Avoiding heap thrashing

If a variable-size heap is in use (for example, -Xms is different from -Xmx), the application may run into a scenario where too many allocation failures are occurring, but the heap doesn't expand. This is heap thrashing, and is caused by a heap that's barely large enough to avoid expansion but not large enough to satisfy future allocation failures. Usually, a garbage collection cycle frees up enough garbage for not only the current allocation failure but a substantial number of future allocation requests. But when heap is getting thrashed, each garbage collection cycle frees up barely enough heap to satisfy just the current allocation failure. The result is that the next allocation request leads to another garbage collection cycle, and so on. This scenario can also occur due to lots of short-lived objects.

A possible solution for breaking out of such a cycle is to increase the -Xminf and -Xmaxf values. If -Xminf.5 is used, for example, the heap will grow until there is at least 50% free heap. It makes sense to increase the -Xmaxf value as well. With -Xminf.5, and with the default of 0.6 for -Xmaxf, too much expansion and contraction may result while the JVM attempts to keep the free heap between 50 and 60%. A difference of 0.3 works well, so -Xmaxf.8 would be a good match for -Xminf.5.

If the traces indicate that several expansions are needed to reach a stable heap size, -Xmine can be modified to set the minimum expansion size based on application behavior. The goal is to avoid too many garbage collection cycles by making enough space available to satisfy not only the current request but as many future requests as possible. Using -Xmine, -Xmaxf and -Xminf provides a lot of flexibility in controlling the application's memory usage characteristics.

Mark stack overflow

One of the most important checks using verbosegc is to ensure that no "mark stack overflow" messages occur. The trace below shows the message and its effect.

<AF[50]: Allocation Failure. need 272 bytes, 18097 ms since last AF>
<AF[50]: managing allocation failure, action=1 (0/190698952) 
   (9584432/10036784)>
<GC(111): mark stack overflow>
<GC(111): freed 77795928 bytes in 1041 ms, 43% free (87380360/200735736)>
<GC(111): mark: 949 ms, sweep: 92 ms, compact: 0 ms>
<GC(111): refs: soft 0 (age >= 32), weak 0, final 0, phantom 0>
<AF[50]: completed in 1042 ms>

This message is thrown when, during the mark phase of garbage collection, the number of references causes the JVM's internal "mark stack" to overflow. The garbage collection handling code uses this stack to push all known references during the mark phase, so that it can recursively scan each live reference. The cause of the overflow is too many live objects in the heap (or more accurately, very deeply nested objects), and frequently signals a bug in the application code. Unless it is possible to tweak an external setting to control how many objects are kept live by the application (such as in some kind of an object pool), this problem will need a fix in application sources. Use of a profiling tool is recommended to determine the live references.

If such a large number of live references are unavoidable, concurrent mark might be a viable option here.

Getting rid of finalizers

The trace below shows something very interesting -- without compaction, it took over 2.78 seconds for allocation failure to complete.

<AF[1]: Allocation Failure. need 56 bytes, 0 ms since last AF>
<AF[1]: managing allocation failure, action=1 (0/521140736) 
   (3145728/3145728)>
<GC(1): GC cycle started Thu Aug 29 19:25:45 2002
<GC(1): freed 321890808 bytes, 61% free (325036536/524286464), in 2776 ms>
<GC(1): mark: 2672 ms, sweep: 104 ms, compact: 0 ms>
<GC(1): refs: soft 0 (age >= 32), weak 11, final 549708, phantom 0>
<AF[1]: completed in 2780 ms>

The culprit here is the number of objects that had to be finalized. Finalizers are a bad idea anyway, and though they are unavoidable in certain circumstances, they should be used only as a last resort and only for actions that cannot be done elsewhere. As an example, performing allocations from inside the finalizers should be avoided at all costs.

Avoid very large allocations

Sometimes the problem is caused not by the existing state of the heap, but by allocation failure. For example:

<AF[212]: Allocation Failure. need 912920 bytes, 34284 ms since last AF>
<AF[212]: managing allocation failure, action=2 (117758504/261028856)>
<GC(273): freed 65646648 bytes in 2100 ms, 70% free (183405152/261028856)>
<GC(273): mark: 425 ms, sweep: 89 ms, compact: 1586 ms>
<GC(273): refs: soft 0 (age >= 32), weak 0, final 0, phantom 0>
<GC(273): moved 755766 objects, 43253888 bytes, reason=0, used x4C0 more 
   bytes>
<AF[212]: completed in 2101 ms>

This trace is from a very old JVM (ca130-20010615 to be precise), hence the reason for compaction (shown in red) is given as 0. But still, it took over 1.5 seconds to compact a 256 MB heap! Why is it so bad? Look at the initial request, which is for 912920 bytes -- almost 1 MB.

Any allocation chunk has to be contiguous, and as the heap fills up, finding contiguous chunks of larger sizes becomes more and more difficult. This is not a problem specific to Java; you can run into this issue while using malloc in C as well. The compaction phase of the JVM reduces fragmentation by relocating references whenever possible, but at the expense of freezing the application for a significant time. The above traces show that compaction was carried out, and the total time to allocate such a large chunk ended up over 2 seconds.

The following traces show a worst-case scenario.

<AF[370]: Allocation Failure. need 2241056 bytes, 613 ms since last AF>
<AF[370]: managing allocation failure, action=2 (135487112/1291844600)>
<GC: Wed Oct 16 10:16:46 2002
<GC(455): freed 41815176 bytes in 28663 ms, 13% free (177302288/1291844600)>
<GC(455): mark: 3233 ms, sweep: 328 ms, compact: 25102 ms>
<GC(455): refs: soft 0 (age >= 32), weak 0, final 17, phantom 0>
<GC(455): moved 15822115 objects, 615093008 bytes, reason=1, used x698 
   more bytes>
<AF[370]: managing allocation failure, action=3 (177302288/1291844600)>
<AF[370]: managing allocation failure, action=4 (177302288/1291844600)>
<AF[370]: clearing all remaining soft refs>
<GC(456): freed 176216 bytes in 3532 ms, 13% free (177478504/1291844600)>
<GC(456): mark: 3215 ms, sweep: 317 ms, compact: 0 ms>
<GC(456): refs: soft 16 (age >= 32), weak 0, final 0, phantom 0>
<GC(457): freed 9592 bytes in 23781 ms, 13% free (177488096/1291844600)>
<GC(457): mark: 3231 ms, sweep: 315 ms, compact: 20235 ms>
<GC(457): refs: soft 0 (age >= 32), weak 0, final 0, phantom 0>
<GC(457): moved 2794668 objects, 110333360 bytes, reason=1, used x30 more 
   bytes>
<AF[370]: managing allocation failure, action=5 (177488096/1291844600)>
<AF[370]: totally out of heap space>
<AF[370]: completed in 268307 ms>

The request is for a 2 MB object (2241056 bytes), and even though there are 135 MB (135487112) free out of a 1.2 GB heap (1291844600), a 2 MB chunk could not be located. The search attempted all sorts of things, spending over 268 seconds, but could not find such a large chunk. The infamous "totally out of heap space" message is printed, indicating the JVM is out of memory.

The best thing to do here would be to break up the allocation request into smaller chunks, if possible. Larger heap sizes may help, but in most cases they will only delay this problem.

Fragmentation and its causes

Let's look at one of the lines from the last traces again:

<GC(455): freed 41815176 bytes in 28663 ms, 13% free 
   (177302288/1291844600)>

Though over 177 MB of space was free, it was not possible to locate a 2 MB chunk. The reason is that, even though the garbage collection cycle can compact holes in the heap, there are allocations within the heap that cannot be relocated during compaction. For instance, the application might be using JNI to allocate and reference objects or arrays. These allocations are pinned in memory, and they can neither be relocated nor garbage collected until the appropriate JNI call is used to release them. The IBM Java service team can help pinpoint these kind of references, and profiling tools can also be useful in such situations.

Similarly, since class blocks are referenced from outside the heap, they are pinned as well. Even without pinned objects, large allocations will generally cause a heap to get fragmented. Fortunately, this type of severe fragmentation is rare in practice.

Is concurrent mark for you?

If a Java application suffers from variable pause times due to garbage collection, concurrent mark can help minimize the pause variation, making the application behavior smoother. But in some cases, concurrent mark may reduce the throughput of an application. It is recommended you compare the application performance with and without concurrent mark, using identical loads to measure the effect on application performance.

However, looking at verbosegc output for a concurrent mark run can provide a great deal of information about the speedup. There is no need to decipher each and every part of the trace being printed; the interesting parts include how often the concurrent mark was able to scan successfully (EXHAUSTED vs. ABORTED/HALTED), and how much work could be done by the background thread.

The following three traces belong to the same Java application and were created during various stages of a single run. They demonstrate the three different outcomes of a concurrent run.

The first possible outcome is when concurrent mark gets EXHAUSTED:

<CON[3]: Concurrent collection, (3457752/533723648) (3145728/3145728), 
   23273 ms since last CON>
<GC(246): Concurrent EXHAUSTED by Background helper . Target=82856559 
   Traced=57287216 (3324474+53962742) Free=3457752>
<GC(246): Cards cleaning Done. cleaned:13668 (33 skipped). Initial count 
   13701 (Factor 0.142)>
<GC(246): GC cycle started Tue Oct 1 00:05:56 2002
<GC(246): freed 436622248 bytes, 82% free (443225728/536869376), in 218 ms>
<GC(246): mark: 51 ms, sweep: 167 ms, compact: 0 ms>
<GC(246): In mark: Final dirty Cards scan: 43 ms 158 cards (total:127 ms)
<GC(246): refs: soft 0 (age >= 32), weak 0, final 5, phantom 0>
<CON[3]: completed in 230 ms>

This indicates that concurrent mark is working as designed. EXHAUSTED means the background thread was able to complete its work before an allocation failure occurred. Since the background thread scanned 3324474 bytes (while the application threads scanned 53962742 bytes), the background thread was able to get sufficient CPU time to reduce the total mark time. The result was that only 51 milliseconds (ms) were spent in mark phase during STW, and total STW time was just 230 ms. Here it looks pretty good for a 512 MB heap.

Below is an ABORTED concurrent mark run:

<AF[164]: Allocation Failure. need 962336 bytes, 75323 ms since last AF>
<AF[164]: managing allocation failure, action=1 (83408328/533723648) 
   (3145728/3145728)>
<GC(247): Concurrent ABORTED. Target=84703195 Traced=0 (0+0) Free=83408328>
<GC(247): GC cycle started Tue Oct 1 00:06:22 2002
<GC(247): freed 350077400 bytes, 81% free (436631456/536869376), in 896 ms>
<GC(247): mark: 695 ms, sweep: 201 ms, compact: 0 ms>
<GC(247): refs: soft 0 (age >= 32), weak 0, final 7, phantom 0>
<AF[164]: completed in 912 ms>
<CONCURRENT GC Free= 11530600 Expected free space= 11526488 Kickoff=11530370>
< Initial Trace rate is 8.00>

This one is bad. Concurrent mark is ABORTED mainly due to large object allocations and calls to System.gc(). If either of these two happen too often in an application, the application may not be able to benefit from concurrent mark.

Finally, there's a HALTED concurrent mark:

<AF[168]: Allocation Failure. need 139280 bytes, 25520 ms since last AF>
<AF[168]: managing allocation failure, action=1 (11204296/533723648) 
   (3145728/3145728)>
<GC(251): Concurrent HALTED (state=64). Target=118320773 Traced=35469830 
   (14765196+20704634) Free=11204296>
<GC(251): No Dirty Cards cleaned (Factor 0.177)>
<GC(251): GC cycle started Tue Oct 1 00:08:06 2002
<GC(251): freed 385174400 bytes, 74% free (399524424/536869376), in 389 ms>
<GC(251): mark: 274 ms, sweep: 115 ms, compact: 0 ms>
<GC(251): In mark: Final dirty Cards scan: 46 ms 2619 cards (total:225 ms)
<GC(251): refs: soft 0 (age >= 32), weak 0, final 6, phantom 0>
<AF[168]: completed in 414 ms>

HALTED lies between EXHAUSTED and ABORTED in terms of measuring usefulness of concurrent mark; it shows that only partial work could be done. The above traces show that the scan could not complete in time for the next allocation failure. The mark phase took 274 ms in this garbage collection cycle, and the total time has crept up to 414 ms.

The ideal case is when most of the garbage collection cycles are concurrent collections (they are triggered due to concurrent mark completing its work, signified by EXHAUSTED), rather than allocation failures. If an application calls System.gc(), the traces will show a lot of ABORTED lines.

In most of the applications, concurrent mark will lead to better performance and will also help in cases where "mark stack overflow" errors are seen. However, if mark stack overflow is being caused by a bug, the only way out is to fix the bug.

Switches to avoid

The command line switches described below should not be used.

Command line switchDescription
-XnocompactgcThis parameter switches off compaction altogether. Though you might see some short-term benefits in performance gains, eventually the application heap will become fragmented and result in an OutOfMemory error, even if there is sufficient free space on the heap.
-XcompactgcUsing this parameter will lead to each garbage collection cycle carrying out compaction, regardless of whether it's useful. The JVM does a fair job of deciding when to do compaction, and works to delay compaction in the normal mode.
-XgcthreadsThis controls the number of garbage collection helper threads created by the JVM during startup. The default is N-1 threads for an N-processor machine. These threads provide the parallelism in parallel mark and parallel sweep modes.

Conclusion

This has been a whirlwind tour of the garbage collection and heap management capabilities of the IBM JVM. Hopefully, the next verbosegc log will make more sense now!

To summarize my recommendations:

  • Upgrade to the latest available JVM whenever possible. An error you encounter might already have been detected and corrected.
  • Adjust -Xms, -Xmx and -Xminf until the verbosegc output shows an acceptable balance between number of allocation failures and amount of pause time during each garbage collection. Avoid contractions or expansions by using a fixed-size heap.
  • Break down large (>500 KB) requests to smaller chunks, if possible.
  • Do not ignore the "mark stack overflow" messages.
  • Avoid finalizers.
  • See if the application can benefit from concurrent mark.
  • Question whether calls to System.gc() are of any use, and if they are not, remove them.

As you have seen, this topic is not trivial. Your friendly neighborhood IBM Technical Support Team is just a call or e-mail away (the link in Resources is a good place to start). They can go into much more detail than any article can cover about the specific scenarios you might encounter.


Resources

About the author

Sumit Chawla works for IBM's eServer division, providing Java support to ISVs. You can contact him at sumitc@us.ibm.com.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Sample IT projects
ArticleID=10238
ArticleTitle=Fine-tuning Java garbage collection performance
publish-date=01012003
author1-email=sumitc@us.ibm.com
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).