“What is the purpose of finalize() method?” is one of the often asked Java interview questions. The typical answer to it is: “Usual purpose of finalize() method is to perform cleanup actions before the object is discarded”. However, behind the scene, finalize() method are handled in a special way. A small mistake in finalize() method has the potential to jeopardize entire application’s availability. Let’s study it in detail.
Behind the Scene
Objects that have “finalize()” method are treated differently during garbage collection process than the ones which don’t have. During garbage collection phase, objects with “finalize()” method aren’t immediately evicted from the memory. Instead, as the first step, those objects are added to an internal queue of ‘java.lang.ref.Finalizer’. For entire JVM, there is only one low priority JVM thread, by name, ‘Finalizer’ that executes “finalize()” method of each object in the queue. Only after the execution of “finalize()” method, the object becomes eligible for Garbage Collection. Assume if the application is producing a lot of objects which has “finalize()” method and the low priority “Finalizer” thread isn’t able to keep up with executing finalize() method, then significant amount of unfinalized objects will start to build up in the internal queue of ‘java.lang.ref.Finalizer’, which would result in significant amount of memory wastage.
Sometimes because of poor programming practice, “Finalizer” thread may start to WAIT or BLOCK while executing the “finalize()” method. If “Finalizer” thread starts to wait or block, then the number of unfinalized objects in the internal queue of ‘java.lang.ref.Finalizer’ will start to grow significantly, which would result in OutOfMemoryError, jeopardizing entire JVM’s availability.
Example
To illustrate this theory, we wrote a simple sample program
public class SampleObject { public String data; public SampleObject(String data) { this.data = data; } @Override public void finalize() { try { // Sleep for 1 minute. Thread.currentThread().sleep(1 * 60 * 1000); } catch (Exception e) {} } public static void main(String[] args) { long counter = 0; while (true) { new SampleObject("my-fun-data-" + counter); System.out.println("created: " + counter++); } } }
Basically, ‘main()’ method of this class creates ‘SampleObject’ continuously. Interesting part of this program is “finalize()” method. This method puts the current executing thread (i.e. ‘Finalizer’ thread) to sleep for 1 minute. This example illustrates the poor implementation of “finalize()” method.
When we ran the above program with max heap size of 10 mb (i.e. -Xmx10M), it crashed with ‘java.lang.OutOfMemoryError’ after few seconds of launch.
This program crashed with ‘java.lang.OutOfMemoryError’ because: Only after the execution of ‘finalize()’ method, SampleObject can be evicted from the memory. Since ‘Finalizer’ thread is put to sleep, it couldn’t execute the “finalize()” method at the rate in which ‘main()’ method was creating new ‘SampleObject’. Thus memory got filled up and program resulted in ‘java.lang.OutOfMemoryError’.
On the other hand, when we commented out “finalize()” method, program ran continuously without experiencing any ‘java.lang.OutOfMemoryError’.
How to diagnose this problem?
Your application might contain hundreds, thousands, millions of classes. It includes classes from 3rd party libraries and frameworks. Now the question become, how will you identify “finalize()” methods that are poorly implemented? This is a tough question to answer. This is where heap dump analysis tools like HeapHero.io might come handy.
When heap dump was captured from the above program and uploaded to HeapHero.io, it generated this beautiful report with several sections. Section that is of interest to us is: ‘Objects waiting for finalization’.
Attachment 3344
This section of the report shows the amount of memory wasted due to objects waiting for finalization of your application. In this hypothetical example 7.66 MB i.e. 97.2% is the amount of memory that is wasted.
Attachment 3345
When you click on the hyperlink given under ‘What are the objects waiting for finalization?’, you will be able to see the objects waiting to be finalized. Basically, you will be able to see the object tree of “j.l.r.ReferenceQueue” (note this is queue of ‘java.lang.ref.Finalizer’ object that holds the reference of all objects, whose finalize() method needs to be executed). If you drill down the tree it will show the objects that are sitting in the queue waiting to be finalized. Here you can see 2 types of objects that are sitting in the queue:
1. com.petals.finalize.SampleObject.data occupying 56.8% of memory
2. com.petals.finalize.SampleObject occupying 11.5% of memory
BINGO!! These are the objects that are created in our sample program