Last time we talked about setting up GC flags and how to tune your Java application with the proper GC algorithm. This time we're going to focus on how to properly setup your Java heap to help get the best performance possible.
The easiest way to set your heap would be to set a maximum allowable size and let the JVM handle everything else. By using the flag "-Xmx" you can set the heap size. Example would be: "-Xmx1024m" or "-Xmx1g" both set the maximum heap size to 1gigabyte. Just realize that you don't want to set the maximum heap larger than the available memory in your system. Performance will suffer and you'll be worse off than had you used a smaller heap.
While that's a good start, we can clearly do better than that. The JVM also has this notion of the heap being split in multiple sections. One section is the nursery or young generation where all allocations happen. The other section is the tenured space or old generation where long lived objects get promoted from the nursery. We can tune the size of these spaces for better performance depending on how our application allocates objects. By adding the flag "-Xmn" you can set the nursery size, and the remaining heap is then allocated to the tenured space.
This tuning of nursery can be an important source of performance improvements as a nursery collection is significantly faster than a full GC. And if your application creates many short lived objects then you would be better off with a larger nursery than tenured space. If only we could somehow calculate how much nursery our application uses we could tune better. Ah, but we can! Just use the flag "-verbose:gc" which shows each nursery and full GC and how much was collected and how much of the heap is free. And in our case we'll add "-XX:+PrintGCDetails" which increases the level of detail. As an example see:
[GC [DefNew: 910K->12K(960K), 0.0003947 secs] 1239K->341K(5056K), 0.0005048 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 908K->13K(960K), 0.0004003 secs] 1237K->341K(5056K), 0.0005126 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 909K->14K(960K), 0.0005241 secs] 1237K->342K(5056K), 0.0006367 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC [Tenured: 3485K->4095K(4096K), 0.1745373 secs] 61244K->7418K(63104K), [Perm : 10756K->10756K(12288K)], 0.1762129 secs] [Times: user=0.19 sys=0.00, real=0.19 secs]
Those are examples of the output you'll get when using "-verbose:gc" and "-XX:+PrintGCDetails". What does all of that mean? The first three lines are young generation collections. The Nursery is 960k large and had around 909k worth of data before the collection and around 13k after the collection. The total size of the young generation was 5056k and had around 1237k worth of data before the collection and about 341k after the collection. The last line shows a full collection. The tenured space is 4096k and in this case objects were moved into tenured space but none were collected. This points to a bigger problem, where the tenured space is too small and should be increased to help give better performance.
You might have noticed that with "-XX:+PrintGCDetails" you'll get a bit of output when your application ends that looks like:
Heap
def new generation total 960K, used 16K [0x22990000, 0x22a90000, 0x22e70000)
eden space 896K, 0% used [0x22990000, 0x22990810, 0x22a70000)
from space 64K, 22% used [0x22a70000, 0x22a738b0, 0x22a80000)
to space 64K, 0% used [0x22a80000, 0x22a80000, 0x22a90000)
tenured generation total 4096K, used 328K [0x22e70000, 0x23270000, 0x26990000)
the space 4096K, 8% used [0x22e70000, 0x22ec2328, 0x22ec2400, 0x23270000)
compacting perm gen total 12288K, used 152K [0x26990000, 0x27590000, 0x2a990000)
the space 12288K, 1% used [0x26990000, 0x269b6390, 0x269b6400, 0x27590000)
ro space 8192K, 63% used [0x2a990000, 0x2aea3ae8, 0x2aea3c00, 0x2b190000)
rw space 12288K, 53% used [0x2b190000, 0x2b7f83f8, 0x2b7f8400, 0x2bd90000)
This shows you the sizes of the various parts of the heap, how large they were and the amount used at the time the application quit. It's a good snapshot of the application's heap requirements at the end of the run and can help you tune. As an example, if the new generation used is close to the total size of the new generation then you should increase the nursery size using "-Xmn" flag. Again having to garbage collect is expensive, and causes your application to stop executing while the collection happens. The fewer you have to do the faster your application runs. And a full GC is slower than a regular GC. If your application is causing many full GCs to happen and your tenured generation is nearly full then modify the nursery (-Xmn) such that the nursery is a smaller portion of the total heap size.
Stay tuned for part 3 as we discuss how to combine GC flags and heap flags to tune a real application for maximum performance.
-------------------------
--
The information presented in this document is for informational purposes only and may contain technical inaccuracies, omissions and typographical errors. Links to third party sites are for convenience only, and no endorsement is implied.