Level: Introductory Phil Vickers (dwinfo@us.ibm.com), Software engineer, IBM Hursley Lab
25 Mar 2002 Finalizers are a necessary evil, but I strongly recommend that they be used only when no other option is available. In this gem, we'll look at the downside of using finalizers and discuss another programming option that can actually strengthen your Java applications.
Finalizers are time consuming
The very nature of a finalizer demands that it be run "finally." More
explicitly, an object's finalizer method is run only when that object
is eligible for garbage collection (GC). The GC checks all unmarked garbage
objects before they're collected and runs the finalize method for those
objects that have a finalizer. The GC has no choice about this. So, when GC runs, it collects all the "has finalizer" objects into a
queue and runs down that queue invoking the finalizer for each object.
The process of collecting, queueing, and invoking the finalizer for all
those objects takes time, but the real killer is the finalizer itself.
There is no way of knowing what the finalize method will do or how long
it will take. This is the exact opposite of what you want if your intention
is to control precious system resources.
Finalizers are unreliable
Some folks think it's a good idea to use threads, objects, etc., for a transaction or a job,
then use a finalizer to notify themselves when the job or transaction is complete.
It's not a good idea. The Java specification makes no guarantee about finalizers other
than that they are "final" in nature. What this means is that:
- There is no guarantee of the order in which finalizers will be run.
- There is no guarantee of when a finalizer will run.
- There is no guarantee of whether a finalizer will be run at all.
Let's consider the implications of each of these no-guarantee statements.
No guaranteed order: In general, finalizers are processed by
processing the heap and following links between objects. So, the "tree" of
located objects that the GC builds will differ each time the heap is processed.
After all the objects have been located, the GC will find objects eligible for
collection, but they will not be located in the order that your application
processed them.
No guaranteed time: This depends on the design of the JVM you're using.
Generally speaking, however, a JVM is perfectly entitled to bundle up finalized
objects and hand them over to a "processfinalizer" thread. The GC may then defer
scheduling of that thread for various operational reasons. This means
you can't simply "clear down" a finalized object and expect to see it run in
the next GC cycle, or even the one after that. And trying to use a
System.gc() call to make the GC
invoke finalizers won't work. In fact, the call will slug your
performance, as described in my previous tip "Living with the Garbage Collector."
No guarantee it will run at all: We'll use a real-life
example to illustrate this possibility. When GC starts, it has to locate
the Java objects in use. To do this, it creates a set of "root references."
The roots are created by scanning the registers and stacks for all the threads
in the JVM. The root references may well be treated "conservatively." By this
we mean that we are not quite sure that the
datum really is a reference to an object in the Java heap -- it might be data in
an application that just happens to look like a Java object reference. If we
were to assume it was a reference and if, during the GC cycle, we performed heap
compaction, we might move the object and then change its reference. From
the application point of view, a datum would then have been arbitrarily changed by the JVM. So, we treat such a reference conservatively. We trace all the objects
it references, but the actual root object is flagged as not eligible for
collection. Conservative treatment means that we inevitably retain what is true
garbage because we're not quite sure. These situations normally correct
themselves as data is usually transient and what was a conservative reference in
one GC cycle disappears in the next, and the garbage can be swept up. If, however, a conservative reference is static data, then the object it
appears to reference can never be garbage-collected, so its finalizer can
never be run. And this is quite legitimate. This is only one example; there
are other reasons a finalizer may never be run.
Good programming practices
One way to get around the finalizer is to write a "finished" method.
The finished method is called when you're done with a given class object.
A finished method might set all class variables to null (making life easy for the GC:
no links to follow up); ensure you didn't leave any static global references
(which would forever render the object uncollectable); and delete any listeners.
The caller of the finished method could then set the object reference to
null. Why do we want to be sure we delete any listeners? Because many memory-leak
reports stem from lingering listeners. Once you add a listener to an object,
that object is ineligible for GC until the listener has been removed. The code sample below shows how a finished method might deal with an unwanted
listener.
public class fred
extends ....
{
public static int [] intarray;
private Object obj;
fred ()
{
...
addActionListener (this);
...
}
private void finished ()
{
for (int i = 0 ; i < intarray.length ; i++)
{
intarray [i] = null;
}
intarray = null;
obj = null;
removeActionListener (this);
}
}
..
..
public fred bloggs;
..
..
// OK, we're done
bloggs.finished ();
bloggs = null;
|
The finished method makes life so much easier
for the GC. It removed unneeded references so that the GC doesn't have to trace them,
and also ensures that any resources you want freed are under your control and will be
freed in a timely fashion.
Conclusions
Remember these key points about finalizers:
- Finalizers are expensive and interfere with optimal GC.
- Finalizers are unreliable as indicators of object death.
- It is good programming practice to be sparing in your use of finalizers.
About the author  | |  | Phil Vickers is a software engineer at the IBM Hursley Lab.
|
Rate this page
|