GC Tuning Confessions of a Performance Engineer by Monica Beckwith
Once again I have the pleasure to provide you with a recap of the latest Virtual JUG session. This time we're going to talk about Garbage Collection, GC, the automatic memory management facility that makes us and our applications believe that there is infinite amount of memory so long as we don't consume too much of it at once.
Presenting this session was Monica Beckwith. Monica is an independent consultant specializing in optimizing the Java Virtual Machine and Garbage Collectors for enterprise applications. She is a regular speaker at various conferences and has several published articles on topics including garbage collection, the Java memory model and others. Monica led Oracle's Garbage First Garbage Collector performance team, and was named a JavaOne Rock Star. You can follow Monica on twitter @mon_beck. Here's the session on Youtube so you can enjoy it with a single click:
Garbage Collection in the JVM
Garbage collection is one of the corner stones of any modern application platform. The JVM is no exception, the memory that your Java code has access to is managed automatically by the platform. You never explicitly need to specify that you need a certain number of bytes for object allocation and you don't need to free that memory manually. While there are many ways of implementing the automatic memory management, the JVMs garbage collectors are truly state-of-the-art.
In a nutshell, all objects allocated in memory are allocated on the heap. The heap represents all the memory your code has access to. When the free memory on the heaps comes to an end, the algorithms to free memory kicks in to move objects on the heap around to give you more space.
Concurrent and Parallel Garbage Collection
The most important characteristic of garbage collection is probably when and how it operates. Naturally, making memory an automatic abstraction takes its toll on the resources that are available to your application.
If the garbage collector runs in parallel with your application code, even partly, the GC is called concurrent. When the GC uses many threads to parallelize and speed up its work, it is called parallel. In the current JDK 8 implementation the default garbage collector is both parallel and concurrent, which makes it really fast. Let's see what operations a GC performs and talk about why they are important and how they impact the performance of GC.
Marking, Moving, and Compacting
A garbage collector deals primarily with garbage, duh! What this essentially means is that the objects that your application still uses are called live and should still be present after the garbage is collected. The unreachable objects are considered dead and are removed to free the memory. Even this simplistic point of view shows that some bookkeeping is required to distinguish live objects from dead objects. The most straightforward approach is to traverse the graph of objects, starting from some root object to see what instances are still reachable and should be preserved.
There's a caveat though, you really don't want to traverse all the heap all the time to find the live objects, because it's a computationally expensive operation and the platform would rather give these computational resources to your code for actually business useful operations. So it performs its duty in 2 phases: marking and sweeping. During the marking phase the live objects are found. Then these live objects are moved around on the heap into a continuous region of memory making free space also a continuous region of memory for easier use. Think of it as the disk defragmentation for memory.
Generational Garbage Collectors
While it is easier to implement a GC that considers the heap to be a single block of memory, it's not the most performant approach. To avoid going through all the objects again and again, taking up valuable time, the heap is divided into the regions and the objects with certain properties are places into certain regions, which are called the generations. Here's how the Oracle's Garbage Collection Basics tutorial illustrates the generations: All the new objects are put into a single generation called the Young generation. It is an established fact that in most applications, most of the objects don't have a large lifespan. If you allocate an object inside a method, it will most likely die after the method invocation is finished. If objects live long enough they are moved to the Old generation where the GC can be performed less frequently. The garbage collection of the new and old generations are called minor and major garbage collections respectively.
Minor and Major Garbage Collections
A minor garbage collection concerns itself with removing dead objects from the young generation. Because most of the objects there will be unreachable at the time of the collection, this can be a very speedy process. Usually it involves dividing the young generation further into smaller regions, called survivor spaces. Live objects which are still live at the time of the minor collection are moved to a survivor space. If they survive another minor GC, they'll become old and will be moved into the old generation region.
The major garbage collection is run on the old generation. This is typically a slower process, because more objects are probably live there. After all they have lived long enough to get promoted to the old generation.
While the generation approach is tuneable and really effective, it struggles with the larger heap sizes. Since the heap is divided into a small number of parts, these grow with the size of the heap. And a major GC has to stop the world to analyze the whole memory in the old generation, which can take significant time. Especially considering that from the application point of view that is an automatic abstraction that could be invisible.
G1GC is a regionalized, parallel, concurrent, incremental garbage collector that provides more predictable pauses compared to other HotSpot GCs. The main problem of the generational approach to garbage collection is that it's not flexible enough. Indeed, applications become hungrier and hungrier with regards to memory and hardware gets cheaper every day. It is only natural that we want to run JVMs with hundreds GB of memory available.
But this creates a problem with the GC pauses. Imagine a third of that space is the old generation, that can possibly be around 30GB of the objects or mark and move. And the users of the JVM cannot tolerate much longer pauses that on smaller heaps. That's why we cannot give Java much more memory without sacrificing performance or changing how the garbage is collected.
G1GC breaks the heap down beyond the 3-5 generation spaces. In fact it breaks it down into thousands of smaller spaces called regions. This allows the GC to consider every region for cleanup individually and with the proper implementation it can tweak every GC event to be consistently short. This is the main value that G1GC brings to the table -- predictable GC pauses even on larger heaps.
This greatness sadly does not come for free. G1GC has to keep records of which objects in a region are referenced from other objects in other regions, kinda like GC roots. And that requires the bookkeeping to be performed concurrently with your application code. This means that the CPU will have to cater to GC more than before and your application might get a bit less resources.
However, the increased granularity of the region based approach allows GC to be much faster and more importantly adaptive and more tuneable than ever before.
List of G1GC Options
Here are a couple of command line options Monica mentioned that one can use for tuning G1GC.
- -XX:G1HeapRegionSize=n - a power of 2 value between 1M and 32M. Approximately, there should be around 2048 regions on the minimum Java heap size.
- -XX:MaxGCPauseMillis=200 - how long can a GC pause be. This is a suggestion, though G1GC will try to make the pauses shorter than the value. The default values is 200ms.
- -XX:G1HeapWastePercent=10 - how much space can be wasted in a region. The larger the waste, the faster the GC will allocate objects in the new region, rather than try to save these bits of space wasted.
You can find more of the options and a basic guideline to reading the GC logs at the Oracle's blog.
Interview With the Speaker
After the session we had the chance to sit down with Monica for the RebelLabs interview. What impact should G1GC being the default garbage collection in JDK 9 bring to the world? Should we all be worried and what parts of our applications should we test first when thinking of migrating to Java 9? Will we see pauseless garbage collectors in the OpenJDK soon and what areas are the most challenging for GC performance? Watch Monica answer these and other questions in the video below. Also, if you have any questions, ping Monica on Twitter: @mon_beck.
How do you cope with garbage collection in Java? Do you have favorite tools that simplify monitoring and GC-tuning? Share your experiences with us on Twitter at @JRebel_Java
Want to find more Java goodies? Check out our latest resources.
Or, are you looking for more of our Virtual Java User Group sessions? Start with this one on architecting large enterprise Java projects by Markus Eisele.