Effective memory management is essential in Java development. Fortunately, Java automates this task through a process called garbage collection (GC), which helps prevent memory leaks. This article explains Java garbage collection in simple terms, covering its core principles, the System.gc()
method for suggesting garbage collection, the deprecated finalize()
method, and modern ways to manage resources.
Table of Contents
- What is Java Garbage Collection?
- Suggesting Garbage Collection with System.gc()
- Why the Deprecated finalize() Should Be Avoided
- Modern Resource Management Techniques
All Java objects are stored in a memory heap. The heap, also called free storage, is a large pool of unused memory that is specifically allocated for your Java program. Depending on the environment you're working in, the heap can be quite large, but its size is always limited. After all, there is no computer with infinite memory. So if you create many objects without cleaning them up, your program could run out of memory and crash.
1. What is Java Garbage Collection?
Garbage collection refers to the process where Java automatically deletes objects that are no longer needed to free up memory, operating silently in the background.
To understand how garbage collection works, it's important to know when an object becomes eligible for removal. In Java, an object is considered eligible for garbage collection when it's no longer accessible to the program and therefore able to be garbage collected. This happens in two primary cases:
- The object has no more references pointing to it, indicating that no part of the code is actively using it.
- All references to the object have gone out of scope, such as when the object was created inside a method that has finished executing.
2. Suggesting Garbage Collection with System.gc()
Java includes a built-in method to help support garbage collection that can be called at any time: System.gc()
.
public static void main(String[] args) {
System.gc();
}
❗Note: When you call System.gc()
, you are simply suggesting to Java that it should perform garbage collection. It doesn’t guarantee that it will actually do this. The JVM may perform garbage collection at that moment, or it might be busy and choose not to. The JVM is free to ignore the request.
The following example demonstrates this more concretely, by creating Cat
objects and manipulates references to them to demonstrate how garbage collection eligibility works in Java.
public class Cat {
public static void main(String[] args) {
Cat one = new Cat();
Cat two = new Cat();
Cat three = one;
one = null;
Cat four = one;
three = null;
two = null;
two = new Cat();
System.gc();
}
}
Initially, Cat one
and Cat two
are created, each referencing a new Cat
object. Assigning Cat three = one
makes both one
and three
point to the same Cat
object. Setting one = null
does not make that Cat
object eligible for garbage collection, because the variable three
still holds an active reference. Assigning Cat four = one
assigns a null
reference and does not affect garbage collection eligibility. It's only when both one
and three
are set to null
that the first Cat
object becomes eligible for garbage collection, because there are then no more active references pointing to it. Similarly, when two = null
is executed, the second Cat
object becomes eligible, since two
was the only reference to that object. The assignment where two
is a new Cat()
makes the third object collected when method scope is closed. Finally, the line System.gc()
suggests to the JVM that it might be a good time to run the garbage collector. However, it doesn't guarantee that garbage collection will actually occur. Even if garbage collection runs, it simply reclaims the memory.
That is, in short:
- The first
Cat
object becomes eligible for garbage collection afterthree = null
because no other variables reference it. - The second
Cat
object becomes eligible for garbage collection aftertwo = null
because no other variables reference it. - The third
Cat
object becomes eligible for garbage collection after the method ends.
The
System.gc()
is just a request, and the JVM may choose to ignore it.
3. Why the Deprecated finalize()
Should Be Avoided
The finalize()
method in Java was part of the Object
class. This function can be confusing and difficult to use. In a few words, the garbage collector may call the finalize()
method once — or not at all, depending on whether and when garbage collection occurs. If the garbage collector failed to collect an object and tried again later, there was no second call to finalize()
. In fact, it was used as a finalisation mechanism to perform certain actions before the object was destroyed by the garbage collector. However, since Java 9, this method has been deprecated in Object
, as reported in the official documentation:
The finalisation mechanism is inherently problematic.
It is important to know that finalize()
could be executed zero or once. It could be executed twice.
How the finalize()
method worked:
The finalize()
method provided a last opportunity to clean up resources that the object hadn't released, such as closing open files or breaking database connections.
protected void finalize() throws Throwable {
try {
// Perform cleanup, like releasing resources
} finally {
super.finalize();
}
}
Problems with finalize()
method:
- Unreliability: the method was not always called immediately after the object became unnecessary, which led to delays in freeing resources.
- Performance: this slowed down the garbage collector as it required additional work to be done.
- Security: the method could allow unsafe code to be executed while cleaning up objects, which created security risks.
Alternatives:
Instead of finalize()
, other resource management techniques are recommended, such as:
-
try-with-resources
automatically closes resources after use, avoiding the need for garbage collection participation. -
Cleaner
andPhantomReference
provide better control and flexibility for resource cleanup without the issues associated withfinalize()
.
4. Modern Resource Management Techniques
Java includes the try-with-resources statement to automatically close all resources opened in a try
clause. This feature, known as automatic resource management, ensures that resources are properly closed without requiring additional code in finally
blocks. This is especially useful for managing files, databases, and network connections, where you need to ensure that the resource is closed regardless of the success of the operation or exceptions.
void readFile() {
try (FileReader file = new FileReader("file.txt")) {
...
} catch (IOException e) {
System.out.println("An error: " + e.getMessage());
}
}
❗Note: Only a try-with-resources statement is permitted to omit both the catch
and finally
blocks. A traditional try
statement must have either or both.
void readFile() throws IOException {
try (FileReader file = new FileReader("file.txt")) {
...
}
}
When using a try-with-resources statement, you cannot use just any class. Instead, Java requires that the classes you use must implement the AutoCloseable
interface. When you use a try-with-resources statement, the Java compiler automatically generates a hidden finally
block behind the scenes. This implicit finally
block is responsible for calling the close()
method on all resources declared in the try-with-resources statement.
The Cleaner
and PhantomReference
mechanisms provide more flexible ways to perform cleanup actions for objects that are no longer reachable, offering alternatives to the deprecated finalize()
method.
- The
Cleaner
class was introduced in Java 9 as part of thejava.lang.ref
package. It provides a mechanism to register cleanup actions that will be executed when an object becomes unreachable.
import java.lang.ref.Cleaner;
class Resource {
private static final Cleaner cleaner = Cleaner.create();
private final Cleaner.Cleanable cleanable;
public Resource() {
cleanable = cleaner.register(this, new CleanupAction());
System.out.println("Resource allocated.");
}
private static class CleanupAction implements Runnable {
public void run() {
System.out.println("Cleanup actions executed.");
}
}
// Other methods for the resource...
// If you need to explicitly clean up, you could provide a close method
public void close() {
cleanable.clean();
}
}
public class CleanerExample {
public static void main(String[] args) {
Resource resource = new Resource();
// Use the resource. The cleanup action will be called when the resource becomes unreachable
resource = null; // Make the resource eligible for garbage collection
System.gc(); // Suggest garbage collection
}
}
-
PhantomReference
provides a mechanism to detect when an object becomes unreachable and to perform cleanup actions before the memory is reclaimed — similar in purpose tofinalize()
, but more reliable. The key feature ofPhantomReference
is the use of aReferenceQueue
. When an object becomes unreachable, it is automatically added to this queue.
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
class PhantomReferenceExample {
public static void main(String[] args) {
ReferenceQueue<MyObject> queue = new ReferenceQueue<>();
MyObject obj = new MyObject();
PhantomReference<MyObject> phantomRef = new PhantomReference<>(obj, queue);
// Make the object eligible for garbage collection
obj = null;
System.gc(); // Suggest garbage collection
// Check if phantom reference is added to the queue
PhantomReference<MyObject> ref = (PhantomReference<MyObject>) queue.poll();
if (ref != null) {
System.out.println("Phantom reference has been cleared.");
} else {
System.out.println("Phantom reference not yet cleared.");
}
}
}
class MyObject {
// Class implementation...
}
Both approaches, Cleaner
and PhantomReference
, offer a more reliable and controlled way to manage resource cleanup compared to the finalize()
method.
Conclusion
This article has provided an overview of how Java automatically frees up memory using the garbage collector. Objects become eligible for removal when they are no longer referenced or go out of scope. Local variables disappear once their block ends, instance fields become unreachable when the object itself is no longer accessible, and static variables live throughout the entire runtime of the program.
Understanding how garbage collector works is essential for performance optimization, as unintentional memory leaks can lead to OutOfMemoryErrors
and degraded application performance. Instead of relying on the deprecated finalize()
method, Java provides more efficient alternatives: try-with-resources, Cleaner, and PhantomReference — to ensure timely cleanup of resources.
Top comments (0)