Last Updated on July 22, 2023 by KnownSense
Garbage collection is an essential process in modern programming languages, including Java, to manage memory dynamically and automatically deallocate objects that are no longer needed. The Garbage Collector (GC) is a critical component of the Java Virtual Machine (JVM) responsible for reclaiming memory occupied by unused objects, thereby preventing memory leaks and optimizing memory usage.
The main goal of the Garbage Collector is to identify objects in memory that are no longer reachable or referenced by the application and free up the memory occupied by those objects. It performs this task by tracking the usage and references of objects during the program’s execution.
How Does Garbage Collection in Java works?
In Java, the Garbage Collector (GC) is responsible for automatic memory management, freeing up memory occupied by objects that are no longer needed. It works by periodically identifying objects in memory that are no longer reachable or referenced by the program. It performs this task by using a combination of algorithms and techniques to track object usage and references. When an object is no longer reachable, it becomes a candidate for garbage collection.
Memory heap generations
To understand how Java garbage collection works, it’s important to know about the different generations in the memory heap. These generations are split into 3 space:
Eden Space: In Java, the Eden Space serves as a memory pool where object created. When the Eden Space becomes full, the garbage collector takes action by either removing objects that are no longer in use or moving those still in use to the Survivor Space. This area is categorized as part of the young generation in the memory heap.
Survivor Space: Within the JVM, there are two Survivor Spaces known as survivor zero and survivor one. These spaces are also classified as part of the young generation, and they act as holding areas for objects that have survived garbage collection in the Eden Space.
Tenured Space: The Tenured Space is where long-lived objects find storage. Objects that endure a certain number of garbage collection cycles are eventually moved to this space. Comparatively larger than the Eden Space, the Tenured Space is checked less frequently by the garbage collector. It is considered the old generation in the memory heap.
Note: Before Java 8, there was a third area of memory known as permanent generation (perm gen or PermGen) that included required application metadata for the JVM. However, the permanent generation was removed in Java 8. Why? because the PermGen space cannot be made to auto increase. So, it is difficult to tune it. And also, the garbage collector is not efficient enough to clean the memory. Due to the above problems, PermGen has been completely removed in Java 8.
Metaspace
In place of permGen metaspace is introduced. It is a new memory space. The most significant difference is how it handles memory allocation. Specifically, this native memory region grows automatically by default. We also have new flags to tune the memory:
- MetaspaceSize and MaxMetaspaceSize – we can set the Metaspace upper bounds.
- MinMetaspaceFreeRatio – is the minimum percentage of class metadata capacity free after garbage collection
- MaxMetaspaceFreeRatio – is the maximum percentage of class metadata capacity free after a garbage collection to avoid a reduction in the amount of space
Additionally, the garbage collection process also gains some benefits from this change. The garbage collector now automatically triggers the cleaning of the dead classes once the class metadata usage reaches its maximum metaspace size.
Therefore, with this improvement, JVM reduces the chance to get the OutOfMemory error.
How do these distinct memory spaces contribute to optimizing garbage collection efficiency?
Garbage collection primarily takes place in the eden space, as numerous new objects are short-lived and do not require prolonged memory retention. However, continuously checking uncollected objects, especially those that need to persist in the heap for extended periods, would be highly inefficient for the garbage collector. To address this, objects are intelligently relocated to the survivor and tenured spaces. This strategic arrangement allows the garbage collector to reduce the frequency of checks in these areas, as the objects residing there are more likely to have a longer memory lifespan.
The tenured space, being substantially larger than the eden space, fills up less frequently, resulting in reduced garbage collector interventions. Nevertheless, a potential drawback arises from the decreased regularity of checks in the tenured space, which can make it more susceptible to memory leaks. Thus, while the distribution of objects across different memory spaces enhances garbage collection efficiency, it necessitates a careful balance to avoid memory management issues.
Types of Activities in Java Garbage Collection
There are two types of garbage collection:
- Minor or incremental Garbage Collection: occurred when unreachable objects in the young generation heap memory are removed.
- Major or Full Garbage Collection: occurred when the objects that survived the minor garbage collection and copied into the old generation are removed.
Garbage Collection Algorithm- Mark and Sweep
The Java garbage collection process employs a mark-and-sweep algorithm, consisting of two key phases: mark and sweep.
In the mark phase, the garbage collector traverses object trees starting from their roots. Each Java object in the heap initially has a mark bit set to 0 (false). As the garbage collector explores the object hierarchy, it sets the mark bit to 1 (true) for objects that are reachable from the roots. Simultaneously, the mark bits of unreachable objects remain unchanged.
Following the mark phase, the sweep phase takes place. During this stage, the garbage collector traverses the entire heap, identifying and reclaiming memory from objects marked with a bit value of 0 (false). These objects are considered no longer in use and can be safely deallocated.
By employing this mark-and-sweep approach, the Java garbage collection process efficiently identifies and collects unused objects, freeing up memory and maintaining the optimal performance of the application.
Triggers for Java Garbage Collection
Three primary types of events serve as triggers for garbage collection in the Java heap.
- Minor events: These events take place when the eden space becomes full, and objects are subsequently relocated to a survivor space. Minor events specifically occur within the young generation, managing short-lived objects.
- Mixed events: These events are an extension of minor events but with the added ability to reclaim old generation objects. They address the garbage collection needs of both young and old generations, striking a balance between efficiency and memory management.
- Major events: Unlike minor and mixed events, major events encompass more extensive garbage collection operations that clear space in both the young and old generations. Due to their comprehensive scope, major events are more time-consuming, but they play a crucial role in freeing up significant memory space and optimizing the overall performance of the application.
Understanding the different triggers for garbage collection aids developers in fine-tuning memory management strategies and ensuring the efficient operation of Java applications.
Types of Garbage collector
Java provides developers with four distinct options for garbage collectors, each offering its own set of advantages and limitations.
- Serial Garbage Collector: The serial garbage collector is best suited for smaller, single-threaded environments. However, it is not recommended for production use as it takes over the thread during the garbage collection process, causing other processes to freeze. This phenomenon is known as a “stop the world” event. This collector is choice for most applications that don’t have small pause time requirements and run on client-style machines.
To enable Serial Garbage Collector, we can use the following argument:java -XX:+UseSerialGC -jar Application.java
- Parallel Garbage Collector: The parallel garbage collector is the default choice in the JVM. As its name suggests, it utilizes multiple threads to perform garbage collection. By leveraging multiple CPUs, it also enhances throughput, earning the nickname “throughput collector.” Similar to the serial collector, this one also causes application threads to freeze during garbage collection.
If we use this GC, we can specify maximum garbage collection threads and pause time, throughput, and footprint (heap size) using following arguments respectively:-XX:ParallelGCThreads=<N>
, XX:MaxGCPauseMillis=<N>, -XX:GCTimeRatio=<N>, -Xmx<N
To enable Parallel Garbage Collector, we can use the following argument:java -XX:+UseParallelGC -jar Application.java
- Concurrent Mark-and-Sweep (CMS) Collector: The concurrent mark-and-sweep collector, like the parallel garbage collector, employs multiple threads. However, it is dubbed a “low-pause” collector because it minimizes the freezing of application threads, making it more suitable for user-facing applications where “stop the world” events can adversely affect user experience. While it can perform concurrent garbage collection for the old generation, it still needs to pause execution when handling the young generation. Additionally, since the collector’s threads run concurrently with the application’s threads, it consumes more processing power compared to other garbage collectors.
To enable this, we can use following flag:java -XX:+UseParNewGC -jar Application.java
From Java 9, the CMS garbage collector has been deprecated. - Garbage-First (G1) Garbage Collector: The G1 garbage collector follows a distinct approach. Instead of separately collecting the young and old generations, it can perform concurrent collection for both by dividing the heap into numerous spaces, going beyond the traditional eden, survivor, and tenured spaces. This approach allows G1 to clear smaller regions instead of large ones all at once, optimizing the overall collection process. Like the CMS collector, G1 runs concurrently, but it rarely freezes execution and can efficiently collect both the young and old generations simultaneously.
To enable this use argument:
java -XX:+UseG1GC -jar Application.java - Z Garbage Collector: Introduced in Java11, tailored for low-latency applications, ensuring that expensive operations are performed concurrently without causing application thread stoppages for over 10 ms. By utilizing load barriers with colored pointers, ZGC efficiently executes concurrent operations while keeping track of heap usage.
The core concept of ZGC revolves around reference coloring or using metadata bits within references to mark an object’s state. It is well-equipped to handle heaps ranging from 8MB to 16TB in size, and its pause times remain consistent, regardless of the heap, live-set, or root-set size.
Like G1, the Z Garbage Collector also partitions the heap, but it differs in that heap regions can have variable sizes, adding to its flexibility and adaptability.
To enable the Z Garbage Collector in JDK versions below 15, the following argument can be used:java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC Application.java
Starting from version 15 onwards, the experimental mode is no longer required:java -XX:+UseZGC Application.java
How to make object eligible for garbage collection in Java?
Here are some scenarios that make an object eligible for garbage collection:
- Nullifying References: When a reference variable pointing to an object is explicitly set to
null
, the object it was referencing becomes eligible for garbage collection. This happens because the object is no longer reachable through any reference.
Example:
MyClass myObject = new MyClass(); // Creating an object
myObject = null; // Nullifying the reference variable, making the object eligible for garbage collection
- Reassigning References: If a reference variable is reassigned to point to a different object, the original object it was referring to becomes unreachable and can be garbage collected.
Example:
MyClass obj1 = new MyClass(); // Creating object 1
MyClass obj2 = new MyClass(); // Creating object 2
obj1 = obj2; // Reassigning obj1 to point to obj2, making object 1 eligible for GC
- Method Scope: Objects created within the scope of a method become eligible for garbage collection once the method execution completes since their references are no longer accessible outside the method.
Example:
public void myMethod() {
MyClass obj = new MyClass(); // Creating an object within the method
// Method execution completes, and the object "obj" becomes eligible for garbage collection
}
- Object Reaching the End of its Scope: Objects created in local blocks, loops, or conditional statements become eligible for garbage collection once the scope of these blocks is exited since their references go out of scope.
Example:
public void myMethod() {
// Other code here...
{
MyClass obj = new MyClass(); // Creating an object within a block
} // Exiting the block, and the object "obj" becomes eligible for garbage collection
// Other code here...
}
Methods to Request JVM to Run Garbage Collector
Once an object becomes eligible for garbage collection, it is not immediately destroyed by the garbage collector. The JVM runs the garbage collector program periodically, and during this process, eligible objects are cleared. However, we cannot predict exactly when the JVM will trigger the garbage collector.
There are two ways to request the JVM to run the garbage collector:
- Using System.gc() Method: The System class provides a static method
gc()
that allows requesting the JVM to run the garbage collector.
Example:
System.gc(); // Requesting JVM to run the garbage collector
- Using Runtime.getRuntime().gc() Method: The Runtime class allows the application to interact with the JVM it is running in. The
gc()
method can be used to request the garbage collector.
Example:
Runtime.getRuntime().gc(); // Requesting JVM to run the garbage collector
It is important to note that there is no guarantee that either of these methods will immediately run the garbage collector, as the JVM decides when to perform garbage collection based on its internal mechanisms.
Finalization and the finalize() Method
Before destroying an object, the garbage collector calls the finalize()
method on the object to perform cleanup activities. Once the finalize()
method completes, the garbage collector proceeds to destroy the object.
The finalize()
method is present in the Object class with the following prototype:
protected void finalize() throws Throwable
Developers can override the finalize()
method based on their requirements to perform cleanup activities, such as closing connections to a database.
It is crucial to note that the finalize()
method is called by the garbage collector, which is a module of the JVM. The Object class’s default implementation of finalize()
is empty, making it necessary to override the method for resource disposal or other cleanup tasks.
Furthermore, the finalize()
method is never invoked more than once for any object, and if an uncaught exception occurs during its execution, the exception is ignored, and the finalization process for that object is terminated. Therefore, it is essential to handle any potential exceptions within the finalize()
method.
Conclusion
Understanding the Garbage Collector and its behavior is crucial for Java developers, as it impacts the performance and responsiveness of applications. It is essential to tune and configure the GC according to the specific requirements of an application to achieve optimal memory usage and minimize interruptions during garbage collection cycles.