Skip to main content

skip to main content

developerWorks  >  Java technology  >

Gems from e-BIT: Why finalizers should (and can) be avoided

developerWorks
Document options

Document options requiring JavaScript are not displayed


Rate this page

Help us improve this content


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.



Back to top


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.



Back to top


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.



Back to top


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


Please take a moment to complete this form to help us better serve you.



YesNoDon't know
 


 


12345
Not
useful
Extremely
useful
 


Back to top