A Definitive Guide to Java Garbage Collection

Java Garbage Collection

Storage problems occur in the app when there isn’t an efficient memory mechanism in place. Java helps overcome this issue with automated garbage collection. Java’s garbage collection is an example of automatic memory management, which is a key feature of the language. It uses garbage collectors to automatically allocate and deallocate memory. While Java can manage memory on its own, an application may require a tailored garbage collection strategy to suit specific requirements. Top Java development companies implement best practices and adapt to changing requirements to ensure efficient performance. 

This article is your definitive guide to understanding the concept of garbage collection in Java applications. Garbage collection is an automatic process that simplifies development and reduces the risk of memory leaks. It explores the advantages of Java garbage collection, types of garbage collectors, key differences between various GCs, their working, best practices, and more.

1. Define Java Garbage Collection

When Java programs automatically clear unused memory at runtime, it is called Java garbage collection. It is a memory management method that finds and deletes unused objects to free up some memory. The JVM provides a section of memory dedicated to storing Java objects, known as the Java heap.

Java programs are compiled into bytecode and run on the JVM. Java code uses objects that have very short lives. A garbage collector monitors and tracks these objects and reclaims their memory once they are no longer needed. Developers don’t have to break a sweat, since this entire process of garbage collection is completely automatic.

2. Top Benefits of Garbage Collection in Java

Since garbage collection in Java is automatic, it offers numerous benefits. Here, we discuss the most important ones: 

  • Support for Large-Scale Apps: Garbage collection ensures efficient memory management, allowing developers to scale applications effectively, even when they are complex or handle large amounts of data and many user interactions. 
  • Improved Performance: The GC continuously frees up the memory storage by deleting the unused objects. As a result, the execution of Java programs becomes smoother, as garbage collection helps reclaim memory, which is essential for maintaining optimal Java application performance. The garbage collector also handles memory allocation and deallocation, which makes memory operations faster and more efficient, and reduces fragmentation. 
  • Memory Leak Prevention: The automatic garbage collection helps prevent memory leaks. The GC finds and deletes the unused objects that occupy unnecessary space in the storage. Freeing up the memory occupied by an object that a Java program is no longer using helps prevent memory leaks.

3. Types of Java Garbage Collectors

The garbage collector plays a crucial role in efficient memory management. It not only cleans up unreferenced objects from the heap to free space for new objects, but it also helps prevent memory leaks. A Java Virtual Machine can use different types of garbage collectors. This section explores them all in detail. 

3.1 Serial GC

It is the simplest garbage collector (also known as the serial collector), designed specifically for small apps that run in single-threaded environments. As the name suggests, the Serial GC performs all the garbage collection events serially on a single thread. After each garbage collection cycle, it also performs compaction.

Serial GC

Running a serial GC could lead to a “stop the world” event, which pauses the entire app. In real-world scenarios, this is problematic, especially for applications that require low latency. Use the following JVM argument to run the serial GC:

 -XX:+UseSerialGC

3.2 Parallel Garbage Collector

The parallel garbage collector (also known as the parallel collector) works similarly to the serial garbage collector. Although it freezes app threads during garbage collection, it can be used with multithreaded applications that have medium-to-large heaps on multiprocessor systems. 

The parallel GC uses multiple threads, making garbage collection faster. 

If peak performance is the priority for your app and pause times of one second or longer are acceptable, then parallel GC might be the right fit for your project. The garbage collector would run frequently, freezing the app threads to conduct garbage collection through multiple threads. Garbage collection in parallel GC is faster than serial GC.

3.3 CMS (Concurrent Mark Sweep) Garbage Collector

Also known as the concurrent low-pause collector, the CMS garbage collector allows most garbage collection work to run concurrently with application threads. Similar to parallel GC, the CMS garbage collector also utilises multiple threads for minor collections. These concurrent, multithreaded phases help reduce pause times and can free CPU resources more efficiently, which often improves application responsiveness. However, in some cases, CMS can increase overall CPU usage or cause fragmentation and long stop-the-world pauses (for example, during concurrent mode failure), which may degrade application performance.

3.4 Garbage-First (G1) GC

The Garbage-First (G1) GC is a modern, general-purpose garbage collector as an alternative to the CMS GC. It breaks down the heap into small parts and prioritises reclaiming parts or objects with the most garbage, hence the name Garbage-First. For server-side apps, G1 is a great choice as it maintains a balance between low pause times and throughput. Its layout and operation differ significantly from those of other garbage collectors. 

3.5 Z Garbage Collector

Z Garbage Collector is a great option for apps that use a huge heap or need low latency. Its threads can keep running even when the GC performs its operations. In other words, the Java app that freezes when most garbage collectors can remain responsive when ZGC runs. It can also track heap usage with colored pointers. 

Compared to other garbage collectors, ZGC has significantly lower pause times. Partitions made with this collector may vary in size. The ZGC carries out its work in the following stages:

  1. Short stop-the-world phase: Analyses the GC roots to make sure that pause time wouldn’t grow with the heap size. 
  2. Concurrent phase: Involves scanning of object graphs, assessing colored pointers, and marking reachable objects. 
  3. Relocation phase: Cleans up heap memory by moving live objects.

4. Comparing The Types of Java Garbage Collectors

This section compares all the garbage collectors against essential features to help you make an informed decision.

FeaturesSerial Garbage CollectorParallel Garbage CollectorCMS (Concurrent Mark Sweep) CollectorG1 (Garbage First) CollectorZ Garbage Collector (ZGC)
ThreadsSingle threadedMultiple threadedMultiple threadedMultiple threadedMultiple threaded
ThroughputLowerHigherGoodModerateHigh
MemorySmallHighModerateHighVery high
ConcurrentNoNoYes (mostly)Yes (mostly)Yes (fully)
Pause timeLonger pausesShorter than serialShort pausesLow pause timesVery low pause times
LatencyHighHighLowLowUltra-low
TuningEasyModerateDifficultModerateEasy
Supported versionsJava 1.2 and aboveJava 5 and aboveJava 1.4.2 to 13Java 7 and aboveJava 11 and above
Arguments-XX:+UseSerialGCXX:+UseParallelGCXX:+UseConcMarkSweepGC-XX:+UseG1GC-XX:+UseZGC
Use casesSmall apps, single CPUMulti-core processorsApps with minimal pause times, like web servers and trading systemsMulti-processor machines with large heaps and low latencyApps with low latency and large heaps

5. How Does Garbage Collection Work in Java?

Garbage collection in Java runs automatically. It identifies objects and categorises them as valuable or non-valuable; unused objects are then disposed of. If an object is occupying memory but is not reachable from the actively running code, its memory can be reclaimed.

Unlike other languages, Java developers don’t have to control every activity run by objects. The Java Virtual Machine manages memory automatically and invokes the garbage collector when heap usage grows. 

Java utilises the mark-and-sweep algorithm for garbage collection. The garbage collectors in the Java virtual machines run this algorithm in the following phases:

  • Mark: The garbage collector evaluates all the objects in the heap and marks objects that are still reachable as “in use”. Meanwhile, the unused objects are left unmarked for deletion. 
  • Sweep: The GC scans the heap for unmarked objects. Every unmarked object is considered garbage and is reclaimed by the collector. 
  • Compact: After sweeping, the garbage collector compacts the remaining objects and relocates them to reduce fragmentation and make contiguous space available for new objects.

Depending on a Java program’s requirements, the JVM conducts periodic garbage collection. To manually request garbage collection, developers must call the System.gc() method, although this may have some negative implications for system performance.

6. Memory Heap Generational Garbage Collection in Java

For efficient memory management and garbage collection, the Java memory heap is broken down into different generations, each with a specific purpose, such as:

Hotspot Heap Structure

6.1 Young Generation

Every new object is allocated in this region. The young generation is divided into two survivor spaces and an Eden space. Most of these objects are short-lived and are quickly reclaimed by garbage collectors. Once the Eden space is filled, the JVM performs a minor garbage collection to remove unreachable objects. All the surviving objects will be moved to survivor spaces, and the aged objects will be moved to the old generation. 

6.2 Old Generation

Objects that survive multiple garbage collection cycles in the young generation are transferred to the old generation. The objects in this generation have a longer lifespan. Garbage collection in this generation is known as either a full or major GC. Full GCs occur less frequently than young-generation GCs but take more time because they must examine much more data. 

6.3 Permanent Generation (PermGen)

The Java Virtual Machine stores metadata like interned strings, method information, and class definitions in a special area. PermGen is different from other generations and is an entirely separate space from heap memory. It’s a space where normal objects live. The size of the PermGen is fixed. Hence, it can get overloaded and cause memory issues. The latest versions of Java replace PermGen with Metaspace, which can resize dynamically.

7. How to Perform Garbage Collection in Java?

This section gives a practical understanding of how garbage collection works in Java. Here, we run a garbage collection demo in three cycles and observe the outcomes for each. The number of cycles depends on the app’s memory usage.

 public class GarbageCollectionDemo {
 
    public static void main(String[] args) {
        Runtime runtime = Runtime.getRuntime(); // Get the runtime instance
        long afterObjectCreationMemory, afterGcMemory;
 
        for (int cycle = 1; cycle <= 3; cycle++) {
            System.out.println("Cycle " + cycle + ":");
 
            // Create a number of temporary objects
            createTemporaryObjects(300);
 
            // Memory after object creation
            afterObjectCreationMemory = runtime.freeMemory();
            System.out.println("After creating objects: Free Memory = " + afterObjectCreationMemory);
 
            // Suggest garbage collection
            System.gc();
 
            // Memory after calling garbage collector
            afterGcMemory = runtime.freeMemory();
            System.out.println("After GC: Free Memory = " + afterGcMemory);
 
            long memoryFreed = afterGcMemory - afterObjectCreationMemory;
            System.out.println("Memory freed by GC = " + memoryFreed);
            System.out.println("-----------------------------------");
        }
    }
 
    /**
     * Creates a number of temporary objects.
     * These will be eligible for garbage collection immediately after creation.
     */
    public static void createTemporaryObjects(int count) {
        for (int i = 0; i < count; i++) {
            new Object(); // No reference is held, so object is eligible for GC
        }
    }
}
 
public class GarbageCollectionDemo {
 
   public static void main(String[] args) {
   	Runtime runtime = Runtime.getRuntime(); // Get the runtime instance
   	long afterObjectCreationMemory, afterGcMemory;
 
   	for (int cycle = 1; cycle <= 3; cycle++) {
           System.out.println("Cycle " + cycle + ":");
 
       	// Create a number of temporary objects
           createTemporaryObjects(300);
 
       	// Memory after object creation
           afterObjectCreationMemory = runtime.freeMemory();
           System.out.println("After creating objects: Free Memory = " + afterObjectCreationMemory);
 
       	// Suggest garbage collection
           System.gc();
 
       	// Memory after calling garbage collector
           afterGcMemory = runtime.freeMemory();
           System.out.println("After GC: Free Memory = " + afterGcMemory);
 
       	long memoryFreed = afterGcMemory - afterObjectCreationMemory;
           System.out.println("Memory freed by GC = " + memoryFreed);
           System.out.println("-----------------------------------");
       }
   }
 
   /**
    * Creates a number of temporary objects.
    * These will be eligible for garbage collection immediately after creation.
    */
   public static void createTemporaryObjects(int count) {
   	for (int i = 0; i < count; i++) {
       	new Object(); // No reference is held, so object is eligible for GC
       }
   }
}

Output:

Cycle 1:
 
After creating objects: Free Memory = 254741016
 
After GC: Free Memory = 257150048
 
Memory freed by GC = 2409032
Cycle 2:
 
After creating objects: Free Memory = 255807848
 
After GC: Free Memory = 257149952
 
Memory freed by GC = 1342104
Cycle 3:
 
 
After creating objects: Free Memory = 255807704
 
 
After GC: Free Memory = 257149952
 
 
Memory freed by GC = 1342248

8. Java Garbage Collection Best Practices

Developers must adhere to the best practices to make Java garbage collection effective and ensure that Java programs run smoothly. However, the set of garbage collection practices to implement depends on the specific characteristics of each app. Therefore, it is necessary to assess your app regularly and adjust the GC strategy to meet changing requirements. This section discusses the common but most efficient garbage collection practices in Java. 

8.1 Select the Right Collector

Picking the right collector makes all the difference. For example, the Parallel GC is more throughput-oriented but causes “stop-the-world” events more frequently. This collector is appropriate for back-end processing, where long pauses from garbage collection are acceptable.

8.2 Leverage Parallelism and Concurrency

You can improve the app’s performance by using parallelism and concurrent garbage collection. When parallel collectors run activities on multiple threads, pause times are shorter. Meanwhile, concurrent collectors minimise interruptions by running concurrently with app threads. Using these collectors can help deliver a responsive and seamless user experience. 

8.3 Minimise Object Creation

It is best to avoid creating objects unnecessarily. Creating many objects, especially inside loops, increases GC activity and can slow down your Java program. If possible, reuse the objects instead of recreating similar objects. This also helps reduce the load on the garbage collector.

8.4 Tune Garbage Collection Parameters

Understanding garbage collection parameters is a must. You may need to adjust them to meet changing requirements. Tuning GC settings—such as collection intervals, thread counts, and the size of different generations—has a strong influence on your app’s performance. 

8.5 Avoid Manual Triggers

Apart from the basic operation of garbage collection, you also have to understand that GC in Java is non-deterministic. In other words, you can’t predict exactly when the garbage collection process will start during the runtime. Developers can suggest that the JVM perform garbage collection using System.gc() or Runtime.gc() methods. Still, there are no guarantees about exactly when the garbage collection process will be executed. 

8.6 Optimise Heap Size

Optimising heap size improves garbage collection. A heap of optimised size doesn’t require useless or more frequent collections. An optimised heap size also eliminates the danger of “running out of memory” situations. Assess how the heap size handles the load under varying conditions to effectively customise and optimise the size for your app’s memory. That can be fulfilled with performance testing or profiling. 

8.7 Monitor and Analyse Garbage Collection Logs

Conduct regular checks on GC logs to understand how the garbage collector is performing. Use monitoring and analytical tools to monitor the collectors. It helps you find potential problems like long or frequent pauses and quickly implement necessary adjustments. JProfiler, VisualVM, and other Java profiling tools can help monitor and analyse the behaviour of Java garbage collectors and identify potential performance bottlenecks. 

9. Conclusion

Garbage collection is critical in Java for reclaiming unused memory, preventing memory leaks, and optimising app performance. Java’s garbage collection is automatic, so developers don’t have to hassle with manual memory management. However, it is important for developers to have an in-depth understanding of garbage collection in Java and implement best practices to ensure maximised outcomes. This helps prevent any issues and maintain the reliability and stability of Java applications.

profile-image
Rakshit Toke

Rakshit Toke is a Java technology innovator and has been managing various Java teams for several years to deliver high-quality software products at TatvaSoft. His profound intelligence and comprehensive technological expertise empower the company to provide innovative solutions and stand out in the marketplace.

Comments

Leave a message...