Journey of Java’s Garbage Collector so far!

This article covers the journey of Khachara Seth (Garbage Collector) of Java from 1.7 to the latest!

Pravinkumar Singh
8 min readDec 25, 2023

In this world of Java programming, its Garbage Collector (GC) plays a crucial role. It helps in cleaning up unused Java objects and freeing up memory. This process, known as garbage collection, is key to the smooth running of your programs.

Now the story doesn’t end there. GC has evolved over time, starting from its first version in Java 1.1, released in 1996 by Sun Microsystems, working in Mark-and-Sweep algorithm, to Java 1.8 with four types of collectors — Serial, Parallel, CMS, and G1. Each has its own way of managing memory, and understanding them is important for any Java developer.

The journey continues with the introduction of new collectors in later versions — Z Garbage Collector (ZGC) in Java 9, Shenandoah in Java 12, and Epsilon in Java 15. Each new collector brings different benefits and challenges, affecting how your applications perform.

In this article, we’ll explore the evolution of Java’s Garbage Collector from Java 1.7 to the latest version. We’ll look at how each collector works, their pros and cons, and how to choose the right one for your needs. We’ll also include code examples to make this exploration practical and useful.

Even if you don’t know Java, you can still understand this article. So, grab some coffee (I suggest black coffee)and get ready for a deep dive into Java’s Garbage Collector.

Understanding Garbage Collector and its working

When you run a program, it creates objects to store data. But not all objects are useful all the time. Some become useless after a while. GC is the process of finding these useless objects and getting rid of them to free up memory.

So, how does GC work? Imagine you’re cleaning up a room. You’d look around to see what’s not needed anymore, like empty coffee cups or Lays wrappers, and throw them away. GC does something similar.

First, it looks at all the objects in your program. It identifies which ones are still being used and which ones are not. This is called “marking”.

Next, it goes through the “sweep” phase. Here, it gets rid of the objects that are not being used anymore, freeing up memory.

Finally, it organizes the remaining objects to make the best use of space. This is called “compacting”.

Example :

public class GarbageCollectionExample {
public static void main(String[] args) {
String str1 = new String("Hello");
String str2 = new String("World");

// reassigning the reference variable
str1 = str2; // Now "Hello" object is eligible for garbage collection

// nullifying the reference variable
str2 = null; // Now "World" object is eligible for garbage collection
}
}

In this code, we first create two String objects, "Hello" and "World". Then, we make str1 point to the same object as str2, leaving the "Hello" object without any references. This makes the "Hello" object eligible for garbage collection. Finally, we set str2 to null, which makes the "World" object eligible for garbage collection as well.

1.7

In Java 1.7, there were three main types of garbage collectors: Serial, Parallel (also known as Throughput), and Concurrent Mark Sweep (CMS).

Serial Garbage Collector: This is the simplest type of garbage collector. It works by stopping all application threads (this is known as a “stop-the-world” event) while it identifies and removes unused objects.

  • Pros: Simple to use, minimal resource requirements, good for single-threaded environments.
  • Cons: Causes application pauses, not suitable for multi-threaded applications or applications with large data sets.

Parallel Garbage Collector: This collector is similar to the Serial Garbage Collector, but it’s designed to work with multiple threads and processors. It’s called the “Throughput” collector because it aims to maximize the amount of work an application can do over a long period of time.

  • Pros: Better performance than the Serial collector in multithreaded environments.
  • Cons: Still causes application pauses, which can be longer than those caused by the Serial collector.

Concurrent Mark Sweep (CMS) Garbage Collector: This collector is designed to minimize the pauses caused by garbage collection by doing most of its work concurrently with the application threads. It’s a good choice for applications that require a lot of memory and have strict pause time requirements.

  • Pros: Minimizes application pauses, good for applications that require a lot of memory and have strict pause time requirements.
  • Cons: More CPU-intensive than other collectors, can lead to fragmentation over time.

Code Example :

# To use the Serial collector
java -XX:+UseSerialGC MyApplication

# To use the Parallel collector
java -XX:+UseParallelGC MyApplication

# To use the CMS collector
java -XX:+UseConcMarkSweepGC MyApplication

1.8

Naturally, each new version of Java brings improvements. The Oracle team, now the owner of Java, aimed to enhance the Garbage Collector by reducing the GC pause times and better-supporting applications with large heap sizes compared to the previous collectors. To achieve this, they introduced a new collector known as G1 (Garbage First).

G1 (Garbage First) Garbage Collector

The G1 Garbage Collector is the new addition in Java 1.8. It’s designed to support larger heap sizes and minimize pause times. It works by dividing the heap into regions and prioritizing the collection of the regions that contain the most garbage.

  • Pros: Supports larger heap sizes, minimizes pause times, compacts free space without lengthy pauses.
  • Cons: Can use more CPU resources than other collectors, may not be suitable for applications with less than 50% heap occupancy.

Code example :

# To use the G1 collector
java -XX:+UseG1GC MyApplication

9 to 11

Guess what, With the release of another series of new versions, the PMs at Oracle weren’t entirely satisfied with the performance of the Garbage Collector [:duh]. They aimed to push the boundaries even further. Therefore, the primary goal of the changes in garbage collection from Java 9 to Java 11 was to further reduce pause times and enhance the performance of applications with large heap sizes.

Z Garbage Collector (ZGC)

ZGC is a scalable, low-latency garbage collector. It’s designed to meet the demands of applications that require large amounts of memory and strict pause time requirements. ZGC achieves this by performing most of its work concurrently, without stopping application threads.

while both G1 and ZGC are designed to handle large heap sizes and minimize pause times, ZGC is designed to provide more predictable and shorter pause times, regardless of the heap size. However, ZGC can consume more CPU resources than G1, so the choice between the two will depend on the specific requirements and constraints of your application.

  • Pros: Supports large heap sizes, minimizes pause times, and performs most of its work concurrently with application threads.
  • Cons: As an experimental feature in Java 9 to 10 and fully integrated in Java 11, it may not be as mature or stable as older collectors. It can also consume more CPU resources than other collectors.

Code Example :

java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC MyApplication

12 to 14

Dammn, yet another collector was introduced in Java 12 and fully integrated by Java 14, known as Shenandoah. You see as time progresses, Java applications need to become more performant and faster. This means they consume more memory and require quicker cleanup of unused memory. Shenandoah was developed to meet these evolving needs.

Shenandoah works by dividing the heap into regions and evacuating live objects from selected regions in parallel with the running application threads. This approach allows Shenandoah to compact the heap more efficiently, reducing the likelihood of memory fragmentation.

One of the key differences between Shenandoah and ZGC is how they handle memory fragmentation. Shenandoah uses a more aggressive approach to compacting the heap, which can lead to less memory fragmentation compared to ZGC. This can be beneficial for long-running applications where memory fragmentation can become an issue over time.

  • Pros: Supports large heap sizes, minimizes pause times, performs most of its work concurrently with application threads, and reduces memory fragmentation.

Code Example :

# To use the Shenandoah collector
java -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC MyApplication

15 & onwards

This version introduced a new experimental garbage collector, Epsilon, and brought significant enhancements to the Z Garbage Collector (ZGC).

Epsilon Garbage Collector: Epsilon is an experimental, no-op garbage collector. It handles memory allocation but does not implement any actual memory reclamation mechanism. Once the application exhausts all available memory, the JVM will shut down. Epsilon is useful for performance testing and scenarios where you have a short-lived job that can fit into the allocated memory.

  • Pros: Provides predictable performance by eliminating GC pauses, useful for performance testing and short-lived jobs.
  • Cons: Does not reclaim memory, leading to OutOfMemoryError when the application exhausts all available memory.

Code Example :

# To use the Epsilon collector
java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC MyApplication

Z Garbage Collector Enhancements: Java 15 brought several enhancements to ZGC, such as concurrent class unloading and the removal of the experimental status, making ZGC a fully supported feature in Java 15.

  • Pros: Concurrent class unloading reduces pause times even further, fully supported in Java 15 and onwards.
  • Cons: Can consume more CPU resources than other collectors.

Factors to consider when choosing a Garbage Collector

When choosing a garbage collector for your application, consider the following factors:

  • Heap Size: Some garbage collectors, like G1, ZGC, and Shenandoah, are designed to handle large heap sizes efficiently.
  • Pause Time Requirements: If your application has strict requirements on pause times, consider using a garbage collector that does most of its work concurrently with the application threads, like CMS, G1, ZGC, or Shenandoah.
  • Throughput Requirements: If maximizing throughput is a priority, consider using the Parallel Garbage Collector.
  • Memory Overhead: Some garbage collectors, like ZGC and Shenandoah, can consume more memory than others.
  • CPU Overhead: Garbage collectors that do a lot of work concurrently with the application threads, like ZGC and Shenandoah, can consume more CPU resources.

Okay now that you have learnt about GC and its evolution, let’s understand how can we use it in our applications.

Diagnosing and Analyzing Your Application’s Garbage Collection

Step 1: Enable Garbage Collection Logging

The first step is to enable garbage collection logging. This can be done by adding the -Xlog:gc*:file=gc.log JVM option when starting your application:

java -Xlog:gc*:file=gc.log MyApplication

This command will create a gc.log file that contains detailed information about each garbage collection event. The -Xlog:gc* option enables logging of all garbage collection details.

Step 2: Run Your Application

Next, run your application under a typical workload. This will generate garbage collection events that will be logged to the gc.log file. It's important to simulate a realistic workload to ensure that the garbage collection behaviour you observe is representative of what your application will experience in production.

Step 3: Analyze the Garbage Collection Log

After running your application, analyze the gc.log file. This file contains information about when garbage collection events occurred, how long they took, and how much memory was freed.

There are several tools available that can help you analyze garbage collection logs. For example, GCViewer is a free, open-source tool that can visualize garbage collection logs.

Look for patterns in the log. For example, if you see frequent full garbage collections, your application might be creating too many short-lived objects. If you see long pause times, your heap might be too large.

Step 4: Adjust Your Garbage Collection Settings

Based on your analysis, adjust your garbage collection settings. This might involve choosing a different garbage collector, adjusting the heap size, or changing the ratio of young generation to old generation size.

Here’s an example of how you can adjust the heap size:

# Set initial heap size to 512M and maximum heap size to 2G
java -Xms512M -Xmx2G MyApplication

The -Xms option sets the initial heap size, and the -Xmx option sets the maximum heap size.

Step 5: Repeat the Process

Garbage collection tuning is an iterative process. After adjusting your settings, repeat the process: run your application, analyze the garbage collection log, and adjust your settings based on your analysis.

If you still reading this article then I appreciate your time and I hope this was useful for you.

Peace!

Reference :

  1. Oracle’s Java Garbage Collection Basics
  2. Oracle’s Java SE 8: Garbage Collection Tuning Guide
  3. OpenJDK’s Garbage Collection features and improvements
  4. Java Garbage Collection Distilled

--

--