Behind the Scenes of the Finalize() Method
Let's look under the hood to see how the finalize methods work, the role of the finalizer thread in the JVM, and how to avoid OOM errors.
Join the DZone community and get the full member experience.
Join For Free“What is the purpose of the finalize() method?” is an often asked Java interview question. The typical answer to it is: “The usual purpose of the finalize() method is to perform cleanup actions before the object is discarded.” However, behind the scenes, finalize() methods are handled in a special way. A small mistake in finalize() has the potential to jeopardize entire application’s availability. Let’s study it in detail.
Behind the Scenes
Objects that have “finalize()” methods are treated differently during garbage collection than ones that don’t have it. During garbage collection, objects with “finalize()” methods aren’t immediately evicted from memory. Instead, as the first step, those objects are added to an internal queue of ‘java.lang.ref.Finalizer’. For the entire JVM, there is only one low priority JVM thread, ‘Finalizer’, that executes the “finalize()” methods of each object in the queue. Only after the execution of the “finalize()” method does the object become eligible for garbage collection.
Assume that if the application is producing a lot of objects that have “finalize()” methods and the low priority “Finalizer” thread isn’t able to keep up with executing those finalize() methods, then a significant amount of unfinalized objects will start to build up in the internal queue of ‘java.lang.ref.Finalizer’, which would result in a significant amount of memory wastage.
Sometimes, because of poor programming practices, the “Finalizer” thread may start to WAIT or BLOCK while executing the “finalize()” method. If the “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 an OutOfMemoryError, jeopardizing the 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, the ‘main()’ method of this class creates ‘SampleObject’ continuously. The interesting part of this program is the “finalize()” method. This method puts the currently executing thread (i.e. the ‘Finalizer’ thread) to sleep for 1 minute. This example illustrates a poor implementation of “finalize()” method.
When we ran the above program with a max heap size of 10 MB (i.e. -Xmx10M), it crashed with ‘java.lang.OutOfMemoryError’ after a few seconds of launch.
This program crashed with a ‘java.lang.OutOfMemoryError’ because only after the execution of the ‘finalize()’ method can SampleObject be evicted from memory. Since the ‘Finalizer’ thread is put to sleep, it can't execute the “finalize()” method at the rate in which the ‘main()’ method was creating new ‘SampleObjects’. Thus, the memory got filled up and program resulted in a ‘java.lang.OutOfMemoryError’.
On the other hand, when we commented out the “finalize()” method, the program ran continuously without experiencing any ‘java.lang.OutOfMemoryErrors’.
How to Diagnose the Problem?
Your application might contain hundreds, thousands, or millions of classes. It includes classes from 3rd-party libraries and frameworks. Now the question becomes, 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. The section that is of interest to us is ‘Objects waiting for finalization’.
Fig 1: Excerpt from the report generated by HeapHero.io
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.
Fig 2: Objects waiting for finalization
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 that this is the queue of the ‘java.lang.ref.Finalizer’ object that holds the reference of all objects whose finalize() methods need 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:
- com.petals.finalize.SampleObject.data occupying 56.8% of memory
- com.petals.finalize.SampleObject occupying 11.5% of memory
BINGO!! These are the objects that are created in our sample program!
Opinions expressed by DZone contributors are their own.
Comments