Garbage Collection in Java - Part 2

Garbage Collection in Java - Part 2

What is GC and How it Works in the JVM

In my previous article, I wrote about Garbage Collection in Java and how they work. In this article, you will learn more about the various types of GC available in Java and their advantages. I will also cover some of the new experimental Garbage Collectors that are available in the latest Java releases.

Types of Garbage Collectors in the Java Virtual Machine

Garbage collection makes Java memory efficient because it removes the unreferenced objects from heap memory and makes free space for new objects.

The Java Virtual Machine has eight types of garbage collectors. Let's look at each one in detail.

Serial GC

This is the simplest implementation of GC and is designed for small applications running on single-threaded environments. All garbage collection events are conducted serially in one thread. Compaction is executed after each garbage collection.

image-68.png When it runs, it leads to a "stop the world" event where the entire application is paused. Since the entire application is frozen during garbage collection, it is not recommended in a real world scenario where low latencies are required.

The JVM argument to use the Serial Garbage Collector is -XX:+UseSerialGC.

Parallel GC

The parallel collector is intended for applications with medium-sized to large-sized data sets that are run on multiprocessor or multithreaded hardware. This is the default implementation of GC in the JVM and is also known as Throughput Collector.

Multiple threads are used for minor garbage collection in the Young Generation. A single thread is used for major garbage collection in the Old Generation.

image-66.png Running the Parallel GC also causes a "stop the world event" and the application freezes. Since it is more suitable in a multi-threaded environment, it can be used when a lot of work needs to be done and long pauses are acceptable, for example running a batch job.

The JVM argument to use the Parallel Garbage Collector is -XX:+UseParallelGC.

Parallel Old GC

This is the default version of Parallel GC since Java 7u4. It is same as Parallel GC except that it uses multiple threads for both Young Generation and Old Generation.

The JVM argument to use Parallel Garbage Collector is -XX:+UseParallelOldGC.

CMS (Concurrent Mark Sweep) GC

This is also known as the concurrent low pause collector. Multiple threads are used for minor garbage collection using the same algorithm as Parallel. Major garbage collection is multi-threaded, like Parallel Old GC, but CMS runs concurrently alongside application processes to minimize “stop the world” events.

image-67.png Because of this, the CMS collector uses more CPU than other GCs. If you can allocate more CPU for better performance, then the CMS garbage collector is a better choice than the parallel collector. No compaction is performed in CMS GC.

The JVM argument to use Concurrent Mark Sweep Garbage Collector is -XX:+UseConcMarkSweepGC.

G1 (Garbage First) GC

G1GC was intended as a replacement for CMS and was designed for multi-threaded applications that have a large heap size available (more than 4GB). It is parallel and concurrent like CMS, but it works quite differently under the hood compared to the older garbage collectors.

Although G1 is also generational, it does not have separate regions for young and old generations. Instead, each generation is a set of regions, which allows resizing of the young generation in a flexible way.

It partitions the heap into a set of equal size regions (1MB to 32MB – depending on the size of the heap) and uses multiple threads to scan them. A region might be either an old region or a young region at any time during the program run.

After the mark phase is completed, G1 knows which regions contain the most garbage objects. If the user is interested in minimal pause times, G1 can choose to evacuate only a few regions. If the user is not worried about pause times or has stated a fairly large pause-time goal, G1 might choose to include more regions.

Since G1GC identifies the regions with the most garbage and performs garbage collection on that region first, it is called Garbage First.

image-88.png

Apart from the Eden, Survivor, and Old memory regions, there are two more types of regions present in the G1GC:

  • Humongous - used for large size objects (larger than 50% of heap size)
  • Available - the unused or non-allocated space

The JVM argument to use the G1 Garbage Collector is -XX:+UseG1GC.

Epsilon Garbage Collector

Epsilon is a do-nothing (no-op) garbage collector that was released as part of JDK 11. It handles memory allocation but does not implement any actual memory reclamation mechanism. Once the available Java heap is exhausted, the JVM shuts down.

It can be used for ultra-latency-sensitive applications, where developers know the application memory footprint exactly, or even have (almost) completely garbage-free applications. Usage of the Epsilon GC in any other scenario is otherwise discouraged.

The JVM argument to use the Epsilon Garbage Collector is -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC.

Shenandoah

Shenandoah is a new GC that was released as part of JDK 12. Shenandoah’s key advantage over G1 is that it does more of its garbage collection cycle work concurrently with the application threads. G1 can evacuate its heap regions only when the application is paused, while Shenandoah can relocate objects concurrently with the application.

Shenandoah can compact live objects, clean garbage, and release RAM back to the OS almost immediately after detecting free memory. Since all of this happens concurrently while the application is running, Shenandoah is more CPU intensive.

The JVM argument to use the Epsilon Garbage Collector is -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC.

ZGC

ZGC is another GC that was released as part of JDK 11 and has been improved in JDK 12. It is intended for applications which require low latency (less than 10 ms pauses) and/or use a very large heap (multi-terabytes).

The primary goals of ZGC are low latency, scalability, and ease of use. To achieve this, ZGC allows a Java application to continue running while it performs all garbage collection operations. By default, ZGC uncommits unused memory and returns it to the operating system.

Thus, ZGC brings a significant improvement over other traditional GCs by providing extremely low pause times (typically within 2ms).

figure2_600w.jpeg

The JVM argument to use the Epsilon Garbage Collector is -XX:+UnlockExperimentalVMOptions -XX:+UseZGC.

Note: Both Shenandoah and ZGC are planned to be made production features and moved out of the experimental stage in JDK 15.

How to Select the Right Garbage Collector

If your application doesn't have strict pause-time requirements, you should just run your application and allow the JVM to select the right collector.

Most of the time, the default settings should work just fine. If necessary, you can adjust the heap size to improve performance. If the performance still doesn't meet your goals, you can modify the collector as per your application requirements:

  • Serial - If the application has a small data set (up to approximately 100 MB) and/or it will be run on a single processor with no pause-time requirements
  • Parallel - If peak application performance is the priority and there are no pause-time requirements or pauses of one second or longer are acceptable
  • CMS/G1 - If response time is more important than overall throughput and garbage collection pauses must be kept shorter than approximately one second
  • ZGC - If response time is a high priority, and/or you are using a very large heap

Advantages of Garbage Collection

There are multiple benefits of garbage collection in Java.

First of all, it makes your code simple. You don’t have to worry about proper memory assignment and release cycles. You just stop using an object in your code, and the memory it is using will be automatically reclaimed at some point.

Programmers working in languages without garbage collection (like C and C++) must implement manual memory management in their code.

It also makes Java memory-efficient because the garbage collector removes the unreferenced objects from heap memory. This frees the heap memory to accommodate new objects.

While some programmers argue in favour of manual memory management over garbage collection, garbage collection is now a standard component of many popular programming languages.

For scenarios in which the garbage collector is negatively impacting performance, Java offers many options for tuning the garbage collector to improve its efficiency.

Garbage Collection Best Practices

Avoid Manual Triggers

Besides the basic mechanisms of garbage collection, one of the most important points to understand about garbage collection in Java is that it is non-deterministic. This means that there is no way to predict when garbage collection will occur at run time.

It is possible to include a hint in the code to run the garbage collector with the System.gc() or Runtime.gc() methods, but they provide no guarantee that the garbage collector will actually run.

Use Tools for Analysis

If you don’t have enough memory to run your application, you will experience slowdowns, long garbage collection times, "stop the world" events, and eventually out of memory errors. This can indicate that your heap is too small, but can also mean that you have a memory leak in your application.

You can get help from a monitoring tool like jstat or Java Flight Recorder to see if the heap usage grows indefinitely, which might indicate a bug in your code.

Default Settings are Good

If you are running a small, standalone Java application, you will most probably not need any kind of garbage collection tuning. The default settings should work just fine.

Use JVM Flags for Tuning

The best approach to tuning Java garbage collection is setting flags on the JVM. Flags can adjust the garbage collector to be used (for example Serial, G1, and so on), the initial and maximum size of the heap, the size of the heap sections (for example, Young Generation, Old Generation), and more.

Select the Right Collector

The nature of the application being tuned is a good initial guide to the settings. For example, the Parallel garbage collector is efficient but will frequently cause “stop the world” events, making it better suited for backend processing where long pauses for garbage collection are acceptable.

On the other hand, the CMS garbage collector is designed to minimize pauses, making it ideal for web based applications where responsiveness is important.

Conclusion

In this article, we discussed the various types of Java Garbage Collectors and how they work.

For many simple applications, Java garbage collection is not something that a programmer needs to consciously consider. However, for programmers who want to advance their Java skills, it is important to understand how Java garbage collection works.

This is also a very popular interview question, both at junior and senior levels for backend roles.

Thank you for staying with me so far. Hope you liked the article. You can connect with me on LinkedIn where I regularly discuss technology and life. Also take a look at some of my other articles and my YouTube channel. Happy reading. 🙂