View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All

Comprehensive Guide to Garbage Collection in Java

Updated on 19/05/20253,961 Views

Memory management is a key part of any programming language—and Java makes it easier with garbage collection. In Java, garbage collection is the process of automatically freeing memory by deleting unused objects. This built-in feature helps developers avoid memory leaks, improve performance, and write cleaner code.

Garbage collection is handled by the Java Virtual Machine (JVM). It makes memory management more efficient and less error-prone compared to languages that require manual cleanup.

In this blog, you'll learn what garbage collection is, how it works in Java programming, why it's important, and which algorithms the JVM uses. Whether you're just starting with Java or want to improve application performance, understanding this process is essential.

Software engineering courses can make these concepts easier to grasp with real-time practice and guidance.

Basics of Garbage Collection in Java

Define Garbage Collection in Java

Garbage collection in Java refers to the automatic process of identifying and removing objects that are no longer reachable or used by an application. This memory management mechanism is integrated into the Java Runtime Environment (JRE) and operates in the background without explicit programmer intervention.

Why Garbage Collection is Necessary

Memory management is a critical aspect of any programming language. Before Java, languages like C and C++ required manual memory allocation and deallocation, which often led to several issues:

  • Memory leaks: Failing to free allocated memory
  • Dangling pointers: Accessing memory that has been freed
  • Double freeing: Attempting to free already released memory

Java's garbage collection eliminates these problems by automating the memory management process. It allows developers to focus on application logic rather than memory concerns.

Take your skills to the next level with these highly rated courses.

Key Concepts in Java Memory Management

To understand garbage collection in Java, you must first grasp these fundamental concepts:

Heap Memory

The heap is the runtime data area from which memory for all class instances and arrays is allocated. It is created when the JVM starts and may increase or decrease in size during application execution.

Memory Area

Description

Garbage Collected?

Heap

Where objects reside

Yes

Stack

Where method frames and local variables reside

No

Metaspace (PermGen in older versions)

Where class metadata resides

Partially

Native Memory

Memory used for JVM code itself

No

Object Lifecycle

Objects in Java go through a predictable lifecycle:

  1. Creation: Memory allocation using the new keyword
  2. In-use: Object is referenced and actively used
  3. Unreachable: No more live references to the object exist
  4. Collection: Object is identified as garbage
  5. Finalization: The finalize() method is called (optional)
  6. Reclamation: Memory is reclaimed and returned to the heap

Object Reachability

Garbage collection in Java determines which objects to collect based on reachability. An object is considered reachable if:

  • It is referenced from a local variable or parameter in a live thread
  • It is referenced from a static field
  • It is referenced from another reachable object
  • It is referenced from a JNI (Java Native Interface) reference

Objects that are not reachable through any of these paths are eligible for garbage collection.

Must read: Stack and Heap Memory in Java: Key Differences Explained

How Garbage Collection Works in Java

The Marking Process

Garbage collection in Java follows a process known as "mark and sweep." This two-phase approach forms the foundation of Java's memory management:

  1. Marking Phase: The garbage collector identifies which objects are still in use (reachable) and marks them.
  2. Sweeping Phase: Unmarked objects (unreachable) are removed, and their memory is reclaimed.

Some advanced garbage collectors add a third phase:

  1. Compaction Phase: After sweeping, the remaining objects may be relocated to eliminate memory fragmentation.

Also read: Comprehensive Guide to Exception Handling in Java

Garbage Collection Roots

To determine reachability, the garbage collector starts from "GC roots" and traverses all object references. GC roots include:

  • Local variables in the stack of a JVM thread
  • Active Java threads
  • Static variables
  • JNI references
  • Classes loaded by the system class loader

Any object that cannot be reached from one of these roots is considered garbage.

Example: Basic Garbage Collection Scenario

public class GarbageCollectionExample {
    public static void main(String[] args) {
        // Object creation
        Object obj1 = new Object(); // obj1 is reachable
        Object obj2 = new Object(); // obj2 is reachable
        
        // obj1 is now unreachable, eligible for garbage collection
        obj1 = null;
        
        // Request garbage collection (though not guaranteed to run immediately)
        System.gc();
        
        System.out.println("Garbage collection requested");
    }
}

In this example, after setting obj1 to null, the original Object instance becomes unreachable and is eligible for garbage collection. The System.gc() call suggests (but does not guarantee) that the JVM perform garbage collection.

Memory Allocation in Java

In modern JVMs, memory allocation is highly optimized. Most allocations occur in thread-local allocation buffers (TLABs), which minimize thread contention and improve performance. When an object is created:

  1. The JVM calculates the required space
  2. Allocates memory from the TLAB if sufficient space exists
  3. If TLAB is full, allocates from the shared heap space
  4. If the heap is full, triggers garbage collection

Different Garbage Collection Algorithms

Several garbage collection algorithms have been implemented in Java, each with distinct characteristics suited to different application requirements.

Mark-Sweep-Compact Algorithm

The Mark-Sweep-Compact algorithm extends the basic mark-sweep approach by adding a compaction phase:

  1. Mark: Identify live objects
  2. Sweep: Remove dead objects
  3. Compact: Rearrange remaining objects to eliminate fragmentation

This algorithm effectively addresses memory fragmentation but requires more processing time due to the additional compaction phase.

Copying Algorithm

The copying algorithm divides the heap into two equal spaces: "from-space" and "to-space":

  1. Allocations occur only in the "from-space"
  2. During garbage collection, live objects are copied to the "to-space"
  3. After collection, the roles of the spaces are swapped

This approach eliminates fragmentation and provides fast allocation but requires twice the memory space.

// Example demonstrating memory behavior with copying collection
public class CopyingGCDemo {
    public static void main(String[] args) {
        // Create a large array that will be copied during GC
        int[] largeArray = new int[1000000]; // Approximately 4MB
        
        // Fill the array with some values
        for (int i = 0; i < largeArray.length; i++) {
            largeArray[i] = i;
        }
        
        // Force a garbage collection
        System.gc();
        
        // Check memory usage after GC
        Runtime rt = Runtime.getRuntime();
        long usedMemory = rt.totalMemory() - rt.freeMemory();
        System.out.println("Memory used after GC: " + (usedMemory / 1024 / 1024) + " MB");
    }
}

Mark-Sweep Algorithm

The original Mark-Sweep algorithm:

  1. Mark: Identifies live objects by tracing references from GC roots
  2. Sweep: Scans the heap for unmarked objects and reclaims their memory

This algorithm is simple but suffers from memory fragmentation over time.

Also read: Thread Lifecycle in Java

Generational Garbage Collection

Generational garbage collection in Java is based on the empirical observation known as the "generational hypothesis," which states that most objects die young. This insight led to the development of a garbage collection strategy that divides the heap into generations:

Young Generation

The Young Generation is where new objects are allocated. It is further divided into:

  • Eden Space: Where new objects are initially allocated
  • Survivor Spaces (S0 and S1): Where objects that survive Eden collections are moved

The Young Generation is designed for rapid collection using a copying collector.

Old Generation (Tenured Generation)

Objects that survive multiple collections in the Young Generation are eventually promoted to the Old Generation. This area is typically larger and collected less frequently using algorithms optimized for longer-lived objects.

Example: Object Promotion

public class GenerationalGCExample {
    public static void main(String[] args) {
        // Fill up the Eden space
        List<byte[]> allocations = new ArrayList<>();
        
        // Allocate many objects, forcing some to be promoted
        for (int i = 0; i < 100; i++) {
            // Allocate arrays of increasing size
            allocations.add(new byte[i * 10000]);
            
            // Force minor GCs occasionally
            if (i % 20 == 0) {
                System.gc();
                System.out.println("Minor GC triggered, iteration: " + i);
            }
        }
        
        // Objects still in the list have survived multiple GCs
        // and have likely been promoted to the Old Generation
        System.out.println("Final allocation count: " + allocations.size());
    }
}

JVM Garbage Collectors

The JVM offers several garbage collector implementations, each designed for different use cases and performance requirements.

Serial Garbage Collector

The Serial GC is the simplest implementation, using a single thread for garbage collection operations. It's suitable for single-threaded applications or those with small heaps.

Key characteristics:

  • Uses mark-copy for the Young Generation
  • Uses mark-sweep-compact for the Old Generation
  • Causes "stop-the-world" pauses when running
  • Activated with -XX:+UseSerialGC

Parallel Garbage Collector

The Parallel GC (also known as Throughput Collector) uses multiple threads for garbage collection operations, reducing collection time.

Key characteristics:

  • Default collector for many JVM versions
  • Focuses on maximizing throughput
  • Uses multiple threads for both Young and Old Generation collections
  • Activated with -XX:+UseParallelGC

Concurrent Mark Sweep (CMS) Collector

The CMS Collector is designed to minimize pause times by performing most garbage collection work concurrently with the application threads.

Key characteristics:

  • Designed for applications requiring low pause times
  • Performs concurrent marking and sweeping
  • Does not compact the heap (can lead to fragmentation)
  • More CPU-intensive than other collectors
  • Activated with -XX:+UseConcMarkSweepGC (deprecated in newer Java versions)

Garbage-First (G1) Garbage Collector

The G1 GC is designed for servers with large amounts of memory. It divides the heap into regions and prioritizes collection in regions with the most garbage.

Key characteristics:

  • Default collector since Java 9
  • Balances throughput and latency
  • Predictable pause times
  • Incremental compaction
  • Activated with -XX:+UseG1GC

Z Garbage Collector (ZGC)

The ZGC is designed for applications requiring low latency and can handle heaps from a few gigabytes to multi-terabyte sizes.

Key characteristics:

  • Concurrent collector with pause times not exceeding 10ms
  • Scales well with heap size
  • Available since Java 11, production-ready in Java 15
  • Activated with -XX:+UseZGC

Shenandoah Garbage Collector

Shenandoah is a low-pause-time garbage collector that reduces GC pause times by performing more garbage collection work concurrently with the running Java program.

Key characteristics:

  • Similar goals to ZGC
  • Concurrent compaction
  • Not included in Oracle JDK, but available in OpenJDK
  • Activated with -XX:+UseShenandoahGC

Advanced Garbage Collection Example

Let's examine a more complex example that demonstrates garbage collection behavior:

import java.util.ArrayList;
import java.util.List;

public class AdvancedGCExample {
    // A class with a finalizer to demonstrate finalization
    static class DataObject {
        private byte[] data = new byte[100000]; // 100KB
        
        @Override
        protected void finalize() throws Throwable {
            System.out.println("DataObject being finalized");
            super.finalize();
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        // Configure VM arguments to see GC details:
        // -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xmx256m
        
        List<DataObject> objects = new ArrayList<>();
        
        // Phase 1: Create objects that stay in memory
        System.out.println("Creating permanent objects...");
        for (int i = 0; i < 10; i++) {
            objects.add(new DataObject());
        }
        
        // Phase 2: Create temporary objects that should be collected
        System.out.println("Creating temporary objects...");
        for (int i = 0; i < 10000; i++) {
            DataObject temp = new DataObject();
            // temp becomes eligible for GC at the end of each loop iteration
        }
        
        // Request garbage collection
        System.out.println("Requesting garbage collection...");
        System.gc();
        
        // Give finalizers a chance to run
        Thread.sleep(1000);
        
        // Phase 3: Create more objects to potentially trigger promotion
        System.out.println("Creating more objects...");
        for (int i = 0; i < 10000; i++) {
            if (i % 1000 == 0) {
                objects.add(new DataObject()); // Some objects are retained
            } else {
                DataObject temp = new DataObject(); // Most are temporary
            }
        }
        
        // Final GC to clean up
        System.out.println("Final garbage collection...");
        System.gc();
        
        System.out.println("Retained objects: " + objects.size());
    }
}

Output:

Creating permanent objects...

Creating temporary objects...

Requesting garbage collection...

DataObject being finalized

DataObject being finalized

...

Creating more objects...

Final garbage collection...

DataObject being finalized

DataObject being finalized

...

Retained objects: 20

This example demonstrates:

  1. Objects that remain referenced (in the objects list) are not collected
  2. Temporary objects are eligible for collection once they go out of scope
  3. The finalize() method is called before objects are reclaimed
  4. Multiple garbage collection cycles occur with different characteristics

Garbage Collection Tuning and Optimization

Key JVM Garbage Collection Parameters

Parameter

Description

Example Value

-Xms

Initial heap size

-Xms1g

-Xmx

Maximum heap size

-Xmx4g

-XX:NewRatio

Ratio of Old/Young generation sizes

-XX:NewRatio=2

-XX:SurvivorRatio

Ratio of Eden/Survivor space sizes

-XX:SurvivorRatio=8

-XX:MaxGCPauseMillis

Target maximum GC pause time

-XX:MaxGCPauseMillis=200

-XX:GCTimeRatio

Ratio of GC time to application time

-XX:GCTimeRatio=19

-Xlog:gc

GC logging

-Xlog:gc*:file=gc.log

Common Garbage Collection Issues and Solutions

Issue 1: Frequent Full GC Pauses

Symptoms:

  • Frequent long pauses
  • Application response time spikes

Causes:

  • Undersized heap
  • Memory leaks
  • High allocation rate in the Old Generation

Solutions:

  • Increase heap size
  • Use concurrent collectors (G1, ZGC)
  • Identify and fix memory leaks
  • Tune generation sizes

Issue 2: High CPU Usage During GC

Symptoms:

  • High CPU usage
  • Decreased throughput

Causes:

  • Too frequent garbage collections
  • Inappropriate collector choice

Solutions:

  • Increase heap size
  • Reduce object allocation rate
  • Select an appropriate collector
  • Tune collector-specific parameters

Issue 3: OutOfMemoryError: Java heap space

Symptoms:

  • Application crashes with OutOfMemoryError
  • Gradually increasing memory usage

Causes:

  • Memory leaks
  • Insufficient heap size
  • Excessive object creation

Solutions:

  • Analyze heap dumps with tools like VisualVM or MAT
  • Increase maximum heap size
  • Fix memory leaks

Example code to generate a heap dump on OutOfMemoryError:

// Run with: java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/dump.hprof MemoryLeakExample
public class MemoryLeakExample {
    private static final List<byte[]> leakyList = new ArrayList<>();
    
    public static void main(String[] args) {
        try {
            while (true) {
                // Keep adding 1MB arrays to the list
                leakyList.add(new byte[1024 * 1024]);
                Thread.sleep(10);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (OutOfMemoryError e) {
            System.err.println("OutOfMemoryError occurred: " + e.getMessage());
            // A heap dump has been created at the specified path
            // Analyze it with tools like VisualVM or MAT
        }
    }
}

Check out: Multithreading in Java

Garbage Collection in Java- Monitoring and Analysis

JVM Monitoring Tools

Several tools are available for monitoring garbage collection:

  1. JConsole: Built-in JVM monitoring tool
  2. VisualVM: Visual tool for monitoring and profiling Java applications
  3. Java Mission Control (JMC): Comprehensive monitoring suite
  4. GC Viewer: Specialized tool for analyzing GC logs

Interpreting GC Logs

Enabling GC logging provides valuable insights:

# Modern JVM (Java 9+)
-Xlog:gc*:file=gc.log:time,uptime:filecount=5,filesize=10M

# Older JVM versions
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log

Sample GC log entry (G1 GC):

[2023-05-01T15:30:45.123+0000][gc,start ] GC(36) Pause Young (Normal) (G1 Evacuation Pause)

[2023-05-01T15:30:45.123+0000][gc,task ] GC(36) Using 8 workers of 8 for evacuation

[2023-05-01T15:30:45.128+0000][gc,phases ] GC(36) Pre Evacuate Collection Set: 0.1ms

[2023-05-01T15:30:45.128+0000][gc,phases ] GC(36) Evacuate Collection Set: 4.2ms

[2023-05-01T15:30:45.128+0000][gc,phases ] GC(36) Post Evacuate Collection Set: 0.5ms

[2023-05-01T15:30:45.128+0000][gc,phases ] GC(36) Other: 0.3ms

[2023-05-01T15:30:45.129+0000][gc,heap ] GC(36) Eden regions: 25->0(25)

[2023-05-01T15:30:45.129+0000][gc,heap ] GC(36) Survivor regions: 4->4(4)

[2023-05-01T15:30:45.129+0000][gc,heap ] GC(36) Old regions: 16->18

[2023-05-01T15:30:45.129+0000][gc,heap ] GC(36) Humongous regions: 5->5

[2023-05-01T15:30:45.129+0000][gc,metaspace ] GC(36) Metaspace: 35868K->35868K(1081344K)

[2023-05-01T15:30:45.129+0000][gc ] GC(36) Pause Young (Normal) (G1 Evacuation Pause) 50M->27M(256M) 5.1ms

[2023-05-01T15:30:45.129+0000][gc,cpu ] GC(36) User=0.03s Sys=0.00s Real=0.01s

Key metrics to analyze:

  • Frequency of collections: How often GCs occur
  • Duration of pauses: Impact on application responsiveness
  • Memory reclaimed: Effectiveness of collections
  • Promotion rate: How quickly objects move to the Old Generation

Conclusion

Garbage collection in Java represents a fundamental advancement in programming language design, automating memory management and eliminating entire classes of errors common in manual memory management systems

While the concepts are straightforward, the implementation details and tuning options provide developers with powerful tools for optimizing application performance.

From the basic mark-sweep algorithm to sophisticated collectors like ZGC and Shenandoah, Java's garbage collection ecosystem continues to evolve to meet the demands of modern applications.

By understanding how garbage collection works internally, recognizing different collector algorithms, and applying proper optimization techniques, developers can harness the full potential of Java while maintaining optimal memory utilization and application responsiveness.

FAQs

1. When does garbage collection occur in Java?

Garbage collection in Java occurs when the JVM determines that memory needs to be reclaimed. This typically happens when:

  • The heap or a generation within the heap reaches a certain occupancy threshold
  • An explicit System.gc() call is made (though this is only a suggestion)
  • The JVM is under memory pressure

The exact timing is non-deterministic and depends on the JVM implementation and garbage collector being used.

2. Can I force garbage collection in Java?

You can request garbage collection using System.gc() or Runtime.getRuntime().gc(), but the JVM is not obligated to honor this request immediately or at all. It treats these calls as suggestions rather than commands. Relying on explicit garbage collection calls is generally discouraged as the JVM's automatic collection is usually more efficient.

3. How can I prevent an object from being garbage collected?

To prevent an object from being garbage collected, maintain a strong reference to it from a reachable object. Common approaches include:

  • Storing the object in a static field
  • Adding it to a long-lived collection
  • Keeping it as an instance field in a long-lived object

4. What's the difference between finalize() and try-with-resources?

The finalize() method is an object method called by the garbage collector before reclaiming an object's memory. It's unpredictable, not guaranteed to run, and considered legacy.

The try-with-resources statement is a language feature for automatically closing resources that implement AutoCloseable. It's deterministic, immediate, and the recommended approach for resource management.

5. How do weak references affect garbage collection?

Weak references (java.lang.ref.WeakReference) don't prevent their referents from being garbage collected. If an object is only weakly reachable (no strong or soft references), it can be collected in the next GC cycle. This is useful for implementing caches and preventing memory leaks.

6. Why does my application have high memory usage even after garbage collection?

Several factors can contribute to high memory usage after garbage collection:

  • Memory fragmentation
  • Long-lived objects that cannot be collected
  • Memory leaks from unclosed resources
  • Native memory usage not subject to garbage collection
  • Large heap size configurations

7. How do I choose the right garbage collector for my application?

Choosing a garbage collector depends on your application's requirements:

  • For maximum throughput: Parallel GC
  • For low latency/responsiveness: G1 GC or ZGC
  • For small applications with limited resources: Serial GC
  • For balanced performance: G1 GC (default in recent Java versions)

Consider your hardware, application characteristics, and performance requirements when making this decision.

8. Does garbage collection improve application performance?

Garbage collection improves developer productivity by automating memory management but can introduce performance overheads:

Pros:

  • Prevents memory leaks
  • Eliminates manual deallocation bugs
  • Allows developers to focus on business logic

Cons:

  • "Stop-the-world" pauses can affect responsiveness
  • CPU overhead from collection activities
  • Memory overhead for tracking object references

Modern collectors balance these tradeoffs effectively for most applications.

9. How do I detect memory leaks in a Java application?

Memory leaks can be detected through:

  • Monitoring increasing memory usage over time
  • Analyzing heap dumps (using tools like MAT or VisualVM)
  • Using profilers to track object creation and lifetime
  • Enabling verbose GC logging and looking for ineffective collections
  • Using JVM flags like -XX:+HeapDumpOnOutOfMemoryError

10. How does garbage collection work with large object heaps?

Large object heaps present challenges for garbage collection:

  • Longer pause times during "stop-the-world" phases
  • Increased memory fragmentation
  • Higher CPU usage during collection

Modern collectors like G1 and ZGC are designed for large heaps:

  • G1 divides the heap into regions and collects them incrementally
  • ZGC can handle multi-terabyte heaps with pause times under 10ms
  • Both use concurrent collection techniques to minimize pauses

11. Why shouldn't I call System.gc() in production code?

Calling System.gc() explicitly is generally discouraged because:

  • It may trigger a full (potentially expensive) garbage collection
  • Modern JVMs are better at determining optimal collection times
  • It can disrupt the JVM's own collection heuristics
  • It may cause unnecessary application pauses
  • It doesn't guarantee memory will be freed
image

Take the Free Quiz on Java

Answer quick questions and assess your Java knowledge

right-top-arrow
image
Join 10M+ Learners & Transform Your Career
Learn on a personalised AI-powered platform that offers best-in-class content, live sessions & mentorship from leading industry experts.
advertise-arrow

Free Courses

Explore Our Free Software Tutorials

upGrad Learner Support

Talk to our experts. We are available 7 days a week, 9 AM to 12 AM (midnight)

text

Indian Nationals

1800 210 2020

text

Foreign Nationals

+918068792934

Disclaimer

1.The above statistics depend on various factors and individual results may vary. Past performance is no guarantee of future results.

2.The student assumes full responsibility for all expenses associated with visas, travel, & related costs. upGrad does not provide any a.