The finalize() method of the Java class object, which can be overridden by any Java class, was deprecated as of Java 9. It will eventually be discontinued. Why?
This article takes a look at exactly how the finalize() method works, when it’s run, and how badly things can go wrong. Using this method can result in unclosed resources, wasted Java heap space, and ultimately to OutOfMemoryErrors.
Let’s see how this can happen, how to diagnose finalize() problems, and what we should do instead of using finalize().
Troubleshooting OutOfMemoryError: Java Heap Space
Before we start, if you’re not familiar with troubleshooting OutOfMemoryErrors, you may like to read this article: Java OutOfMemoryError: Heap Space. It gives some background into how to understand, find and fix memory issues.
Another useful article on this topic deals with the 9 different types of java.lang.OutOfMemoryError.
Unclosed Resources
Let’s now take a look at what we mean by unclosed resources.
Examples of resources include:
Heap and native memory;
File Descriptors;
Operating system handles;
Network sockets;
Database connections.
Many classes that manage these resources, such as the Stream classes, have a close() method. This releases any associated resources. It’s important to call this method when the resources are no longer needed. If they don’t have one, we must ensure the objects are released for garbage collection in a timely manner.
During the early years of Java, we were told that it was good practice to include these cleanup operations in a finalize() method, which would ensure all resources were released when the object was garbage collected.
Fairly soon, this was found to be unreliable, as we’ll see in the next section.
When is finalize() Executed?
To keep redundant memory from building up, a JVM process known as the garbage collector (GC) runs in the background. On each cycle, it:
Works from GC roots to identify and mark all memory items that still have live references;
For each unmarked item, the GC marks it as finalizable. If it has a finalize method, it’s added to the finalize queue; if not, GC marks it as eligible for deletion.;
Removes all objects eligible for deletion.
The JVM has a single finalize queue, which is not multi-threaded. It works sequentially through the queue, running the finalize() method of each object. Once the finalize() method has completed, the object is marked as eligible for deletion, and it will be removed on the next GC cycle.
At best, an object that has a finalize() method, and all its children, remain in memory for two GC cycles instead of just one. If it has a large retained memory, this can be significant.
It’s highly possible it will remain in memory for much longer. If a lot of objects are waiting to be finalized, it may be a while before they get to the front of the queue, and for all this time, they are retaining unneeded memory. This becomes more serious if a queued object is holding locks that may be needed by other processes, or unclosed resources.
At worst, a single object in the queue may have a slow finalize() method, and the entire queue is held up until it completes. If it hangs waiting for a resource or a lock to be available, it can result in a memory leak and, eventually, an OutOfMemoryError.
The other danger with using the finalize() method is that it may, in fact, never be executed at all. The object may still be in the queue when the program exits. If finalize() was used for important actions, such as database commits, this could result in data loss.
The bottom line is: the developers of the JVM cannot guarantee when, or even if, the finalize() method will be run. It’s therefore been recognized as dangerous, and that’s why it has been deprecated.
How To Diagnose Finalizer Problems
Let’s take a small sample program that demonstrates the effect of slow finalizers. This application creates several instances from a class, BuggyClass, that deliberately has a hanging finalizer.
// This demonstrates a hanging finalizer, and how it causes memory to be retained
// even though a weak reference is used.
// ==============================================================================
import java.lang.ref.WeakReference;
public class BuggyProg {
// Public class creates an object from TestClass
// =============================================
public static void main(String[] args) {
TestClass a = new TestClass();
}
}
// Test class creates a number of instances of BuggyClass using weak references
class TestClass {
public TestClass() {
WeakReference<BuggyClass> a=null;
WeakReference<BuggyClass> b=null;
WeakReference<BuggyClass> c=null;
WeakReference<BuggyClass> d=null;
WeakReference<BuggyClass> e=null;
WeakReference<BuggyClass> f=null;
WeakReference<BuggyClass> g=null;
WeakReference<BuggyClass> h=null;
WeakReference<BuggyClass> i=null;
methodA(a);
methodA(b);
methodA(c);
methodA(d);
methodA(e);
methodA(f);
methodA(g);
methodA(h);
methodA(i);
try
{Thread.sleep(150000);}
catch (Exception ex){}
}
public void methodA(WeakReference a) {
a = new WeakReference<BuggyClass>(new BuggyClass());
}
}
// BuggyClass creates a large array, and has a hanging finalizer
// It prints a message to indicate when the finalize method is run
// ===============================================================
class BuggyClass {
int[] a = new int[2000000];
public BuggyClass() {
}
public void finalize() {
System.out.println("Finalizing");
while(true)
a[1]=100;
}
}
This program creates several instances of the class BuggyClass, which is released very quickly for garbage collection. This is because they’re created with weak references.
BuggyClass deliberately has a finalizer that hangs. It displays a message ‘Finalizing’ when the finalizer runs.
Let’s see what happens when we run the program.
c:\javadev\articles\demos>java BuggyProg
Finalizing
c:\javadev\articles\demos>
Despite the fact that we created multiple instances of BuggyClass, and made them available for GC, only ONE of these finalize() methods has been initiated. This is because the other instances were still waiting in the finalizer queue when the program completed. They will never be run at all.
So how can we tell if we have objects waiting in the finalizer queue?
The easiest way is to take a heap dump, and run the HeapHero tool. This produces a comprehensive heap analysis report, including an interactive section that lists the finalizer queue. We can drill down from this section to see the actual classes of objects awaiting finalization, as per the image below.

Fig: Finalizer Report by HeapHero
What Should We Use Instead of finalize()?
When we create our own classes, we should use a close() method instead of finalize() for any cleanup operations. Of course, we need to document the fact that any application using this class must use this method to release resources.
When we use other classes, we should always check if they have a close() method, and call it when the task is completed.
Alternatively, we can use the try-with-resources construct to automatically release resources when the block completes.
Conclusion
The finalize() method has been deprecated with good reason. It can cause hard-to-find problems with Java heap space, as well as other issues such as running out of available file descriptors. Instead, we should put any cleanup code into a close() method.
We also need to be meticulous about calling the close() method of resource-hungry classes such as streams, and ensure all our objects are released for garbage collection as soon as we’ve finished with them.
The HeapHero tool is ideal for diagnosing finalizer problems, since it contains an explicit list of any objects in the finalizer queue.
Top comments (0)