Garbage collection is faster than other standard library functions at allocating memory, and dead objects cost nothing to collect. GC finds all the dead objects without developer assistance. But most collectors cause GC-related pauses proportional to the size of their heaps.
In this post you will learn:
- The different types of garbage collectors
- What’s unique about the Azul Platform Prime C4 Collector
- More about tuning garbage collection
During application execution, the JVM frees unused memory when objects are no longer used. This process is called garbage collection (GC).
Garbage collection is much better and more efficient than you might think. It’s faster than other standard library functions at allocating memory, and dead objects cost nothing to collect. GC finds all the dead objects without developer assistance. But in some ways, garbage collection is also insidious. Most collectors cause GC-related pauses proportional to the size of their heaps.
______________________________________________________________________________
1 gigabyte of live objects = 1 second pause
______________________________________________________________________________
In other words, a larger heap means a longer pause. And if you run a 20-minute test and tune until all the pauses go away, congrats on delaying the pause until the 21st minute. The pause will still happen, and your application will still suffer. And garbage collection doesn’t even eliminate object leaks — the developer still must find and fix references holding those leaked objects.
But back to the good news. Java does provide some GC control. Developers and architects can make decisions to adjust application performance, due to the behavior of the garbage collector.
Trying to solve garbage collection at the application programming layer is dangerous. It takes a lot of practice and understanding to get it right, time that could better spent building value-added features. Even if you make all the right decisions, other code your application leverages will probably not be optimized. As the application workload changes over time, your application will still have GC-related performance issues.
Also, depending on the characteristics of your application, choosing the wrong garbage collector type or using the wrong settings can greatly increase pause times or even cause out-of-memory crashes. With a proper understanding of garbage collection and what your available options are, you can make better informed decisions that can improve the performance and reliability of your application at runtime.
Are all Java garbage collectors the same?
Garbage collectors are divided into several types. Each is more or less concurrent, meaning it runs while the application pauses (not concurrent), overlaps application execution (somewhat or mostly concurrent), or while the application runs (concurrent).
- Concurrent collector – performs garbage collection concurrently while application execution continues.
- Parallel collector – uses multiple threads. A collector can be concurrent but not parallel, and it can be concurrent AND parallel. (Side note – be cautious when researching older literature on garbage collection, since what we used to call parallel is now called concurrent)
- Stop-the-world (STW) – is the opposite of concurrent. It performs garbage collection while the application is completely stopped.
- Incremental – performs garbage collection as a series of smaller increments with potentially long gaps in between. The application is stopped during garbage collection but runs in between increments.
- Moving – the collector moves objects during garbage collection and has to update references to those live objects.
- Conservative – most non-managed runtimes are conservative. In this model, the collector is unsure of whether a field is a reference or not, so it assumes that it is. This is in contrast to a Precise Collector.
- Precise – a precise collector knows exactly where every possible object reference is. A collector cannot be a moving collector without also being precise, because you have to know which references to fix when you move the live objects.
Azul Platform Prime C4 Collector
C4 (Continuously Concurrent Compacting Collector) is the default in Azul Platform Prime. C4 concurrently compacts both the young generation and old generation. Unlike other algorithms, it is not ‘mostly’ concurrent, but fully concurrent, so it never falls back to a stop-the-world compaction.
C4 uses a Load Value Barrier (LVB) to verify each heap reference as correct when loaded. Any heap references that point to relocated objects are caught and fixed in this self-healing barrier. C4 has a guaranteed single pass concurrent marker. No matter how fast your application mutates the heap, C4 can keep up. The C4 collector also performs concurrent reference processing (including weak, soft and final references) and relocation and remapping are both concurrent. C4 also uses ‘quick release’ to make freed memory available quickly to the application and for the relocation of objects. This enables ‘hand over hand’ compaction that does not require empty memory to function.
GC tuning observations
Garbage collection tuning for most collectors is hard to get right, even when you understand the characteristics of your application. The figure below shows two sets of tuning parameters for the CMS collector in the HotSpot JVM. While they may use similar parameter, they are very different and, in some areas, diametrically opposed. Yet the performance of your application could be optimized with either set, depending on its particular characteristics. With most collectors no ‘one size fits all’ answer exists. Developers and architects have to tune garbage collection carefully and retune every time the application, environment or anticipated load changes. Getting these parameters wrong can cause unexpected, long pauses during peak load times.
However, performance of an application on the Azul Zing C4 collector is insensitive to the ‘usual’ tuning parameters. Because it marks and compacts concurrently in both the young and old generations the only important parameter is heap size.
When you set the heap size in the Azul Prime runtime, the C4 collector computes the GC time it needs in order to keep up with allocation rates. You do not need to set ratios or evacuation times. Zing’s C4 GC will fire when needed using threads that are separate from your application threads. This allows worst case pause times to drop by orders of magnitude compared to other GC strategies.
With GC out of the way, time to safepoint emerges as the next dominant source of delays, and even lower latencies may be achieved using other controls, typically in the OS, or in the JVM through JIT compiler adjustment.