Skip to Content

A short Primer to Java Memory Pool Sizing and Garbage Collectors

At first the good news: Java SE 6.0 is doing a wonderful job in picking the right options for a system based on the available system resources. The entire tuning of the various memory pools and picking of the right garbage collectors becomes significant less important compared to older JVM versions.

There is a lot of literature on the web. A good starting point are the SDN performance pages .

Assuming that you don't have the time to read everything in detail. Here's a 30 minute animated

overview using visualgc from the freely available jvmstat package. jconsole (part of SE6.0) will shown as well.

To keep a 10 year garbage collection history short:

The Sun Hotspot VM consist of a number of memory pools which need to be large enough for a given application:

  • Young generation (named as well Eden space): New objects will be allocated in the memory pool. The assumption is that most object get dereferenced and become unreachable soon after their creation. Objects not being dereferenced will be copied by the new generation garbage collector into the survivor spaces. They may get copied n some special cases directly into the old generation pool.

  • Survivor spaces: These two small spaces keep the surviving objects of a young generation garbage collection. Surviving objects will be copied for a (small) number of times from one survivor into the other. This allows to harvest our more dereferenced objects. 

  • Old generation: The largest memory pool which should keep the long living objects. Objects are getting copied into this pool once they leave the survivor spaces.

  • Permament generation: This fairly unknown pool keeps the information of all the classes. It doesn't need any attention for most applications. It may need to be adapted for some applications with many classes. It may need some attentention as well if the application permanently loads and unloads classes.

The entire tuning of these pools is based on some view principles:

  • The new generation should be large enough to keep all temporary objects until they are getting dereferenced.

  • The old generation should host all permanent objects

  • Young generation garbage collections (GC) are unavoidable. Their run time typically grows with the number of objects.

  • The young generation GC is typically cheap compared to the old generation GC

  • Old generation GC should be avoided. They're needed to clean up objects which aren't permanent, yet some always slip through the young generation GC.

The typical strategy for users who can't rely on the the auto adaptive sizing options is the following:

  • Increase the young generation until the runtime of the GC becomes unacceptable or all temporary objects can be cleaned up in the new generation.

  • Increase the old generation until around at least 50% remain free after a GC or the runtime of the GC run times becomes unacceptable.

The best way to get a feeling for these decisions is to get a quantitative understanding of the application by attaching visualgc or a jconsole. The other way to monitor an application is adding the option -verbose:gc to a VM which will force the VM to prompt all garbage consoles on console.

 

The examples used here is Specjbb, an application which is growing it's data set in three steps every minute.

The examples are being run on a two processor Sun Blade 2000. There a number of things to look for on the screen:

  • a graphical CPU monitor (sdtprocess) is running in the left upper corner. Blue means the first CPU is getting used. The red range indicates that the second CPU is getting used. Missing pixels means that CPU cycles aren't getting used. Single threaded garbage collections for the VM to use one processor for the GC leaving the other processor unused. The goal is to keep the CPUs of the system busy as much as possible.

  • Visualgc will show the sizes of all pools and how the fill up over time

  • The console output from -verbose:gc will show from time to time “Full GC”. This is a last resort for the VM to clean up objects. They should be avoided whenever possible. The parallel new (generation) GC and the mostly concurrent GC (CMS) will only switch back to full GC when they are in trouble.

Example 1: An undersized VM with not enough memory

The first example shows a sequence where a fixed 60MB new generation and a fixed 120MB old generation are by far to small to host the application. Many objects are spilling over from the young generation. The old generation will fill up quickly. The system will spend most of the time with inefficient garbage collections. A visual GC pattern like below is an application which is in deep trouble. The application is getting interrupted very often by the garbage collector and the garbage collector is not able to free to free enough memory. The GC is a kind of busy all the time without being able to release enough memory.

Example 2: A VM with an increased old generation and a to small young generation

The second example shows a VM which has an increased old generation of 320MB. The permanent full GCs are now gone. The system isn't trashing anymore. The new generation is still not able to keep the temporary objects. Many temporary objects are spiling over into the old generation. The filling degree of the old generation goes up. The system is now able to use the two processors for most of the time. Full GCs can be recognized by the sharp decrease in old generation filling level. The application is blocked through the full GCs. This means hat all in flight transactions for user are paused. The response times will be very bad while a full GC os gettin processed.

Example 3: A VM setup with parallel new generation and mostly concurrent garbage collections

The third example is now enabling a parallel new generation GC which is able to use both processors at a time and the mostly concurrent GC for the old generation. This is the typical combination which will work well with interactive systems. The parallel new GC will shorten the interruptions to the application by using all processors. The mostly concurrent GC will use a thread in the back ground to do the job. The second processor is still available to the application. It remains however a sort single threaded period. Visualgc is documenting this algorithm by an old heap which is slowly decreasing while the application still progresses. The examples however shows what happens if the old generation becomes overcrowded towards the end of the run. The mostly concurrent GC leaves a fragmented memory space. It can' compact the heap. It has to fall back to a compacting full GC. Monitor this event in your application. You can't avoid it completley. It should be a rare event (a few times a day).

Example 4: A VM sized as before yet with an increased young generation

The fourth example fixes the problems of the over spilling objects from the young generation by increasing to 120MB. The video shows how this strategy works for the first third of the run. The object creation rate increases through the second and the third phase. The old generation starts filling up again. Old generation GCs are kicking in in the later phases.

This increased load is a typical issue of a real application. The new generation needs to be adapted with increasing load. he reason is:

  • The new generation pool can keep a certain number of objects
  • Objects have their average live time

Creating more objects per time (increasing load, more users etc.) means that there is less space. Less space means the objects is copied in the (expensive to clean up) old generation pool earlier. The old GC has overproportional work to do since there are dereferenced objects which didn't show up before and the load increased in parallel. 

Example 5: A well sized VM with increased threshold levels for survivor space with a short jconsole demo

The last example is now fixing this problem as well. One option would have been to increase the young generation furthermore. A different solution is to increase the maximum allowed filling degree of the survivor spaces. Setting this value to 90% warrants for this application that all temporary objects get unreferenced within 4 young generation garbage collections. The things to watch out in the video below are:

The lowest window shows the survivor histograms. The surviving objects in the second and third generation become visible in this demo.

  • Old generation Gcs: There are no more Gcs after the system stabilized.

  • The example: A 5 minute flash video pulled from the screen.

  • The options used are: -XX:MaxNewSize=120m -XX:NewSize=120m -Xms320m -Xmx320m -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:TargetSurvivorRatio=90

Summary

The Java SE 6 Hotspot VM is doing an outstanding job to optimizes it's behaviour based on the existing hardware platform. The configuration of the maximum heap size ( -Xmx option) will be sufficient for most applications.

I hope that readers are going to play around with visualgc and jconsole in order to get a feeling of the quantitative aspects of object allocation and deallocation inside their application. Visualgc is my personal compagnion since it allows to me to understand the state of the VM with one look while passing by a monitor. Jconsole or the Netbeans 6.0 profilers are the tools for a deeper understanding.

Please keep in mind that I had to misconfigure the VM in the first 4 examples in order to be able to demonstrate bad situations. SpecJBB has been my exercise application. The benchmark has a very limited memory foot print and no memory leaks. Everyday applications will look very different based their their memory allocation and deallocation pattern.

Please keep in mind that this little article is just supposed to be a primer for people who never had the time to think about VM configuration. There is a lot of material on the web about advanced tuning.

Have a look for our general performance introduction which lists commonly available performance monitoring tools for Solaris.

The next step to boost your application performance is to save CPU cycles through profiling with Netbeans and visualVM

Comments

Small typo

I guess there's a smalll typo in the line under example 1

"The first example shows a sequence where a fixed 60MB new generation and a fixed 120MB old generation are by far to small to"

This has to be I guess

"The first example shows a sequence where a fixed 60MB new generation and a fixed 120MB old generation are by far <<<<<>>>> small to"

Thanks

This is great!

An excellent overview of patterns of gc and related issues. Also, a fantastic demo of visualgc!

One small typo to report that could mislead. The line "It can' compact the heap" in Example 3 should obviously be "It can't compact the heap". Just thought I may as well report it.

Great job... Thanks very much!

fixed, thank you.

Thank you. This was a correct observation. Very helpful. I fixed the typo.
- Stefan

A superb example with videos

A superb example with videos which shows how the Eden, Survivor & Old Gen spaces are used and the behaviour of GC under different JVM configs. Great job Stefan...truly the best short primer I've ever read!.

A very nice introduction

Thanks! This was a very nice introduction. The examples really tied it together well.



page | about seo