We all take the JVM garbage collector for granted, but how many of us really understand what it does, how it does it, and whether we can tune it in any way to perform optimally for our applications?
Garbage Collector Responsibilities
We talk about the garbage collector as if it just collects garbage (whatever that is supposed to mean). It does more than just that. It is responsible for the memory management for the JVM. This includes the following tasks:
- Decides how heap memory allocation is done.
- Decides on the memory spaces and layouts.
- Decides on size and layout of objects.
- Allocates objects in memory.
- Tracks objects over their lifetimes.
- Collects garbage, i.e. clears any unused objects from memory.
- Reallocates space and memory as required for optimisation (e.g. compacts fragmented memory).
- Adds in JIT compilation code, e.g. new addresses of moved objects.
So choosing the correct garbage collector can affect almost everything in the JVM! Selecting the right one for the task is crucial in achieving better application performance and stability.
A lot of garbage collectors have two generations: young and old. A quick, less accurate collector is used in the young generation. This may leave some garbage in memory. Because the collector is quick, we should try have objects collected in the young generation, i.e. short-lived objects (true for programming too). Objects got promoted to the old generation when they have survived one or a number of GC cycles. They are said to be tenured at this point.
Because memory is so important to the garbage collector, one of the primary tuning options is the -Xmx
flag. This sets the maximum heap size. This gives us the biggest bang for our buck! Usually more memory is better for low pause GCs.
The rule of thumb is to start with -Xmx
set at twice the size of the live set. The live set is the stable size of the heap after garbage collections while running the application.
Garbage Collection Algorithms
There are two JVMs available with the OpenJDK: the Hotspot JVM from Oracle and the OpenJ9 JVM from IBM.
Between them they have 12 different garbage collectors, more than 120 tuning flags, multiple memory spaces and pools, and a huge number of GC logging options.
The Hotspot JVM has seven garbage collectors, while the OpenJ9 JVM has six. One of the garbage collectors, Epsilon, is the same for both JVMs.
Not all of these garbage collectors are available in all versions of Java. As the American car adverts say, “Your mileage may vary.”
We can explicitly specify the GC algorithm using the command line options, or we can use the default GC algorithm set by the Java version. Java 8 uses the Parallel GC as the default GC. Java 9 uses the G1 GC as the default. The default settings will often not be optimal for our applications, but they’re generally “good enough” to run with at first. And definitely a good place to start doing optimization.
It’s always worthwhile trying out a different GC algorithm if our application isn’t meeting its performance targets. The cause of this is mostly from the GC.
We will not be discussing all the GC algorithms now, but it is important to know the command line options to choose a particular GC:We will not be discussing all the GC algorithms now, but it is important to know the command line options to choose a particular GC:
Garbage Collector | HotSpot Parameter |
---|---|
Epsilon | -XX:+UseEpsilonGC |
Serial | -XX:+UseSerialGC |
Parallel | -XX:+UseParallelGC / -XX:+UseParallelOldGC |
CMS | -XX:+UseConcMarkSweepGC / -XX:+UseParNewGC |
G1 | -XX:+UseG1GC |
Shenandoah | -XX:+UseShenandoahGC |
ZGC | -XX:+UseZGC |
Garbage Collector | OpenJ9 Parameter |
---|---|
Epsilon | -Xgcpolicy:nogc |
Throughput | -Xgcpolicy:optthruput |
Balanced | -Xgcpolicy:balanced |
Generational | -Xgcpolicy:gencon |
Metronome | -Xgcpolicy:metronome |
Pause Optimized | -Xgcpolicy:optavgpause |
Garbage Collector | HotSpot | OpenJ9 | Description |
---|---|---|---|
Epsilon | -XX:+UseEpsilonGC | -Xgcpolicy:nogc | terminate, no GC |
Serial | -XX:+UseSerialGC | targeted at 1 CPU | |
Parallel | -XX:+UseParallelGC | throughput | |
Throughput | -Xgcpolicy:optthruput | throughput | |
CMS | -XX:+UseConcMarkSweepGC | pause time | |
G1 | -XX:+UseG1GC | pause time | |
Shenandoah | -XX:+UseShenandoahGC | pause time | |
ZGC | -XX:+UseZGC | pause time | |
Balanced | -Xgcpolicy:balanced | pause time | |
Generational | -Xgcpolicy:gencon | pause time | |
Metronome | -Xgcpolicy:metronome | pause time | |
Pause Optimized | -Xgcpolicy:optavgpause | pause time |
For more details see (from https://xperti.io/blogs/java-vm-options-guide/)
Garbage collection algorithms
- Serial Garbage Collector is the simplest GC implementation. It uses a single thread to perform the garbage collection. It pauses the running application threads until garbage collection is done. It increases the application pause time and decreases the application throughput. Not good for multi-threaded applications.
- Parallel Garbage Collector is also known as the Throughput GC. Similar to the Serial GC, it also pauses the running threads while doing the garbage collection, but uses multiple GC threads instead of a single thread. It is the default garbage collector in Java 8.
- CMS Garbage Collector is known as the concurrent mark-sweep GC. It uses multiple threads to scan the heap memory continuously to mark objects that are unused and then sweep the marked objects away. The CMS GC provides better application throughput, but uses more CPU cycles than the parallel collector.
- G1 Garbage Collector was designed as a server-style garbage collector for multiprocessor machines with a large amount of RAM. The G1 collector is recommended for applications requiring large heaps (~6 GB and over) and short GC pause times below 0.5 seconds. G1 also compacts the free heap space after garbage collection. It is the default garbage collector of Java 9.
- Epsilon GC eliminates garbage collection entirely. It is also known as a “no-op” garbage collector. It handles memory allocation, but doesn’t actually reclaim any memory. When the available heap is full, the JVM terminates with an `OutOfMemoryError`. There are some cases when we know that the available heap will be enough, so we don’t want the JVM to use any resources to run GC tasks. Otherwise we generally only use it for testing. It was introduced in Java 11.
Ending Off
Look out for more interesting Java facts and tips in next week’s post. And don’t forget to share your comments and Java experiences.
And if you found this useful, subscribe to my weekly Java tips!