Understanding Garbage Collection Algorithms in Java

Vivek Singh
Javarevisited
Published in
8 min readOct 12, 2022

--

What is a Garbage Collector ?

A Garbage collector(GC) is a component in JVM which governs the heap of your application. It takes care of allocating memory to objects, destroying them to make space for allocating more objects.

A heap is a chunk of memory which is shared among all threads. In a heap, all class instances(objects) and the array is allocated. The minimum and maximum heap sizes can be set using -Xms=<n> and -Xmx=<m> command-line options.

Use case of Garbage Collection(GC) algorithm?

There are multiple Garbage collection algorithm which can be found. Like for Example JDK8 and prior uses Parallel GC algorithm which focuses on throughput by getting as much work done as possible without considering the latency factor. JDK9+ uses the algorithm Garbage First(G1) GC which tries to balance throughput and latency concerns both. These concepts would become clearer as you go through this article. Patience :)

Anyways, moving on ; The very basic purpose of a Garbage Collection algorithm is :

  1. Should be able to provide memory space to any new request/operations as fast as possible
  2. Should be able to clear memory by destroying objects which are not needed. The process should be fast.
  3. After Step2, when the space is available again, GC should be quick to allocate objects to the free space when needed.

The pause time is the duration during which the garbage collector stops the application to reclaim memory. This variable directly affects latency, so the goal is to limit the longest of these pauses.

The implementation of GC component varies because each algorithm targets a specific performance metric.

The three main performance metrics which are targeted while writing a GC is : throughput, latency, and memory footprint.

Performance Metrics targeted by GC algorithms

  1. Throughput is the amount of work that can be done in a specified time unit. The GC algorithm which does more work in short time is preferable.
  2. Latency is the time taken for a request to complete. The GC algorithm would try to make as little pauses as possible for Garbage collection so that the request doesn’t have to wait for garbage collection i.e. GC algorithm will not make any request/operation wait, it will try to complete the operation as fast as possible
  3. Memory Footprint is the memory needed by a GC process for it to execute smoothly. If GC algo takes more memory, it means that we have less memory available for Heap.

These three metrics are connected: A high throughput collector may significantly impact latency (but minimizes impact on the application) and the other way around. Lower memory consumption may require the use of algorithms that are less optimal in the other metrics. Lower latency collectors may do more work concurrently or in small steps as part of the execution of the application, taking away more processor resources.

Trying to improve a GC in one or more of the metrics often penalizes the others. Obvious , right ? That’s why we have different algorithms targeting different performance factors.

OpenJDK provides five GC algorithms that focus on different performance metrics :

https://blogs.oracle.com/javamagazine/post/java-garbage-collectors-evolution

We will mainly be focusing on 2 main algorithms from the above list : Parallel(used by Java8 and prior) and Garbage First(used by Java9+).

Garbage First GC

G1 GC is the default algorithm used in Java9 and beyond. G1 focuses equally on throughput and latency.
Stop the World concept -is basically pausing the application for a while, so that the garbage collection can work. This algorithm uses the Stop the World(STW) pauses but makes the pauses shorter so that latency could be reduced i.e. the requests/operations doesn’t have to wait longer to be completed.
Making the pauses shorter means the GC algorithm works for very brief shorter durations so that the application can run smoothly without the end user noticing these pauses.

G1 performs this lengthy work concurrent to the application, that is, while the application is running using multiple threads. This decreases maximum pause times significantly, at the cost of some overall throughput.

This G1 algorithm is stable, very mature, and is upgraded all the time with newer ideas. You can request the stop-the-world pauses in this algo to be no longer than x milliseconds. One of the key design goals of G1 was to make the duration and distribution of stop-the-world pauses due to garbage collection predictable and configurable. Generational Garbage Collection plays a key role in switching b/w the two performance metric (Throughput and Latency) for this role. What G1 does is , it divides the Heap memory into 2 parts 1. Young Generation 2. Old Generation. The memory space allocated to Old generation is much more than that of Young generation. The objects which have been active for longer duration are moved from young generation to old generation i.e. When an object has survived a certain number of GCs, the collector moves it to the old generation. Newer objects are allocated to young memory initially. It means that probability of having temporary or short lived objects in young generation is higher. Makes sense ? Because all the objects which have been active since long will be residing in old memory location. So it is logical to check the young generation memory whenever the G1 garbage collection runs. Thus we will free up space from young memory, and this space can be allocated to new requests & operations.

However, old generation memory will be eventually filled up. To tackle this, G1 GC algorithm uses generational garbage collection which means it checks the complete young generation memory for GC, but uses an incremental approach towards the old generational memory. Since the old generational memory would contain more active objects (currently being used by application) so the time taken for GC against this memory would have been relatively larger if not for the concept of generational garbage collection.

How does generational garbage collection work ?

The G1 GC traces the old generational memory and finds out all the live/active objects currently being used by application (It uses table and different Data Structures to get this information ). Now since G1 GC knows the live objects currently active, so the scope of memory in old generation to be checked for garbage collection reduces by a lot as the space which has active objects can’t be cleared or garbage collected.

The regions that contain the most garbage are collected first. Young memory is checked first, and then old memory excluding the active/live object memory is checked. Hence the name: garbage-first collection.

This will eventually reduce latency because the pauses for garbage collections will be very short. The actual memory reclamation, if done all at once, would be very time consuming on large application heaps like in Parallel GC model. Whenever the G1 GC runs, it works on the complete young memory and works on small part of old generation memory over time with every GC cycle, thus freeing up space on old memory over time. So it takes an incremental approach towards old memory by working on smaller chunks of memory in every GC cycle.
Generational garbage collection is complex because the active/live objects uses data structures to be traced. These takes up additional space to keep track of the active objects. However one of the key performance metric of GC i.e latency is reduced by huge margins since pauses for the garbage collection is very short.

Beginning with JDK 9, G1 automatically determines an optimal point at which to start old-generational tracing. Before Java9 this time was chosen by the user manually which was a complex thing to do, since selecting a time too late or early led to performance issues. G1 GC also prioritizes checking the larger objects, as because if it gets cleared then we have more free space to be allocated.

From Jdk8 to Jdk18 there has also been tremendous improvement in Memory Footprint. Memory footprint is the amount of extra space taken by the GC algorithm outside of Heap memory to work smoothly. For G1 GC algorithm , the amount of space needed for Jdk8 was 5.8Gb, and now in Java18 the amount of space needed is 1.25GB.

G1 works best for applications with very strict pause-time goals and a modest overall throughput, such as real-time applications like trading platforms or interactive graphics programs.

Parallel GC

The Parallel GC is the default collector for JDK 8 and earlier. It tries to copy the objects from one location to another in the heap in a more compact form so that we could have more space in memory. There is a concept called Stop The World(STW) pauses. The STW pauses are triggered when there is a new request waiting, and there is no memory to be allocated because heap doesn’t have any space. In this case the JVM will stop the application completely, and will focus on running the GC algorithm with all threads and processes dedicated to GC. Once the space is cleared in Heap, JVM allocates space to the requesting object and finally continues the execution of application. This algorithm focuses on throughput by trying to get more work done in a time unit with minimal focus on latency (pauses). It uses multi threading to get the work done, and usually takes longer pause time because it focuses on throughput. Every pauses are longer, and garbage is collected during these longer pauses. So latency is compromised, as the application requests have to wait during these pauses.

Parallel GC can be selected as default GC algorithm by using below commands against JVM or using it in JVM startup script :

java -XX:+UseParallelGC com.mypackages.MyExecutableClass
java -XX:+UseParallelOldGC com.mypackages.MyExecutableClass
java -XX:+UseParallelGC -XX:+UseParallelOldGC com.mypackages.MyExecutableClass

The ZGC and Shenandoah GCs prefer to make latency the goal by compensating on throughput. They attempt to do all garbage collection work without noticeable pauses. These were first introduced in JDK 15 and JDK 12, respectively, as nonexperimental versions.

Serial GC as the name suggests is serial i.e. to say it uses only 1 thread to get the STW(Stop The World) work done. It cannot take advantage of multiprocessor hardware. This GC algorithm can be used for small, short-running applications because of it’s simple approach and no complexity rule. This algorithm was used by Java5 & Java6.

References :

Conclusion :

Hope this article was helpful in explaining the Garbage Collection mechanism and algorithms. Feel free to reach me out at vivek.sinless@gmail.com or on LinkedIn.

--

--

Vivek Singh
Javarevisited

Software Developer. I write about Full Stack, NLP and Blockchain. Buy me a coffee - buymeacoffee.com/viveksinless