Memory Leak Due To Time Taking finalize() Method

All Objects in Java implicitly inherit from java.lang.Object. This class contains multiple methods, which can be overridden in the child class. One such overridable method is finalize(). Before objects get garbage collected from memory, JVM will execute the finalize(). So, if you need to close any resources that were opened in the class (like backend connections, Files…), it can be done in this method. However, this method has few shortcomings, when it’s not carefully handled it will result in OutOfMemoryError. This post explains those in detail and how to address them.

Note: finalize() method has been deprecated in Java 9 due to several inherent flaws (one of them is exposed in this post). So, refrain from using the finalize() method in your code, instead refer to the solutions given in the ‘What are the alternatives to finalize() methods?’ section of this post.

Poorly implemented finalize() method results in OutOfMemoryError

Here is a sample program which simulates OutOfMemoryError due to improper implementation of finalize() method:

01: public class TimeTakingFinalizer {
02:	
03:    static class SlowFinalizerObject {
04:        
05:    	private String largeString = generateLargeString();    	
06:
07:        @Override
08:        protected void finalize() throws Throwable {
09:            
10:            Thread.sleep(5000); // Simulating slow finalization
11:        }
12:    }
13:
14:    public static void main(String[] args) {
15:        
16:    	int count = 0;
17:        
18:        while (true) {
19:                        	
20:            new SlowFinalizerObject(); // Creating objects rapidly
21:
22:            if (++count % 10 == 0) {
23:                System.out.println("Created " + count + " objects.");
24:            }
25:        }
26:    }
27:    
28:    private static String generateLargeString() {
29:        
30:    	StringBuilder sb = new StringBuilder(5 * 1024 * 1024); // ~5MB
31:        while (sb.length() < 5 * 1024 * 1024) {
32:            sb.append("X");
33:        }
34:        return sb.toString();
35:    }
36:}

Before continuing to read, please take a moment to review the above program closely. In the line #3 ‘SlowFinalizerObject’ is declared, whenever this object is instantiated, in line# 5, a huge string ‘XXXXXXXXXXXXXXXXX…’ is created and assigned to the ‘largeString’ instance variable. In line #8, ‘finalize()’ method is implemented for this object. This method puts the thread to sleep for 5 seconds, to simulate a situation that this method takes a long time to complete. In line #20, ‘SlowFinalizerObject’ is continuously created because of ‘while(true)’ condition in line #18. 

In a nutshell this program is continuously creating ‘SlowFinalizerObject’ and the ‘finalize()’ method of this object takes 5 seconds to complete. When this program is executed, it will result in ‘java.lang.OutOfMemoryError: Java heap space’. However, if ‘finalize()’ method is removed (i.e. line #8 – #11), this program can run continuously fine without any OutOfMemoryError.

Why does the slow finalize() method result in OutOfMemoryError?

To find the answer to this question, we need to understand how JVM executes ‘finalize()’ method:

  1. Whenever an object is eligible for garbage collection and if it implements the finalize() method, it will be added in to an internal queue which is present in java.lang.Finalizer class
  2. For the entire JVM there is one single ‘Finalizer’ thread, that will execute the ‘finalize()’ method of all the objects that are present in the internal queue. 
  3. Say if the ‘finalize()’ method takes a long time to complete (as in this case), ‘Finalizer’ thread will get stuck and the internal queue will start to build up quickly.
  4. When the queue starts to grow beyond the allocated memory size, OutOfMemoryError will be thrown

How to diagnose the finalize() method created by Memory Leak?

You want to follow the steps highlighted in this post to diagnose the OutOfMemoryError: Java Heap Space. In a nutshell you need to do:
1. Capture Heap Dump: You need to capture heap dump from the application, right before JVM throws OutOfMemoryError. In this post, 8 options to capture the heap dump are discussed. You might choose the option that fits your needs. My favorite option is to pass the ‘-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=<FILE_PATH_LOCATION>‘ JVM arguments to your application at the time of startup.

Example:

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/tmp/heapdump.hprof

When you pass the above arguments, JVM will generate a heap dump and write it to ‘/opt/tmp/heapdump.hprof’ file whenever OutOfMemoryError is thrown.

2. Analyze Heap Dump: Once a heap dump is captured, you need to analyze the dumps. In the next section, we will discuss how to do heap dump analysis.

Heap Dump Analysis – finalize() method

Heap Dumps can be analyzed through various heap dump analysis tools such as HeapHero, JHat, JVisualVM… Here let’s analyze the heap dump captured from this program using the HeapHero tool.

Fig: HeapHero flags memory leak using ML algorithm

HeapHero tool uses Machine Learning algorithms internally to detect whether any memory leak patterns are occurring in the heap dump. Above is the screenshot from the heap dump analysis report flagging a major warning that ‘java.lang.ref.Finalizer’ class is occupying 95.61% of overall memory. It’s a strong indication that the application is suffering from memory leak and it originates from the ‘java.lang.ref.Finalizer’ class.

Fig: Largest Objects section highlights java.lang.reg.Finalizer

The ‘Largest  Objects’ section in the HeapHero analysis report shows all the top memory consuming objects as shown in the above figure. You can clearly notice that the ‘java.lang.ref.Finalizer’ class occupies 95.61% of memory. 

Fig: Outgoing Reference section of java.lang.reg.Finalizer

The tool also gives the capability to drill down into the object to investigate their content. When you drill down into the ‘java.lang.ref.Finalizer’ class reported in the ‘Largest Object’ section, you can see all its child objects of this class. From the above figure, you can notice the actual string ‘XXXXXXXXXXXXXXXXXXXXXXX…’ to be reported. Basically, this is the string that was added in line #5 of the above program. Thus, the tool helps you to point out the memory leaking object and its origination source, with ease.

What are the alternatives to finalize() method?

There are few strategies to close the resources instead of the finalize() method. They are:

  1. Use try-with-resources

For objects holding external resources like files or streams, implement AutoCloseable and use them within a try-with-resources block. This guarantees that cleanup logic is executed promptly when the block completes, reducing the chance of lingering memory.

  1. java.lang.ref.Cleaner

Cleaner is a modern, safer replacement for finalize(). It allows you to register a cleanup action that runs after an object becomes unreachable—without blocking garbage collection like finalize() can.

  1. java.lang.ref.PhantomReference

With PhantomReference, you can monitor when an object is ready for collection. By using a reference queue, you can trigger cleanup code outside of the GC thread, avoiding the pitfalls of finalization delays.

  1. Use Explicit Cleanup Methods

Design your API with a clear close() or dispose() method and document the need to call it. This gives developers full control over resource management and avoids the unpredictability of automatic finalization.

Conclusion

From this post, we can understand that a poorly implemented finalize() method has potential to bring down the entire application. By leveraging alternate strategies to finalize() method, and by using tools like HeapHero for faster root cause analysis, you can protect your applications from hard-to-detect outages.

Share your Thoughts!

Up ↑

Index

Discover more from HeapHero – Java & Android Heap Dump Analyzer

Subscribe now to keep reading and get access to the full archive.

Continue reading