Cleaning up after Jini services

Standardize the way Jini clients release finite nonmemory resources

In May 1998, I published "Object finalization and cleanup" in my Design Techniques column of JavaWorld. In this article, I gave advice on designing the end of object lifetimes. In particular, I tried to answer the question, "What the heck are finalizers for?"

The contract of finalize()

A finalizer is a Java instance method named finalize() that returns void and takes no arguments. Class Object declares such a method with the protected permission. The Java specifications make the following promise about finalizers:

Before reclaiming the memory occupied by an object that has a finalizer, the garbage collector will invoke that object's finalizer.

The javadoc page for class Object says the following about the finalize() method's purpose:

The general contract of finalize is that it is invoked if and when the Java virtual machine has determined that there is no longer any means by which this object can be accessed by any thread that has not yet died, except as a result of an action taken by the finalization of some other object or class that is ready to be finalized. The finalize method may take any action, including making this object available again to other threads; the usual purpose of finalize, however, is to perform cleanup actions before the object is irrevocably discarded. For example, the finalize method for an object that represents an input/output connection might perform explicit I/O transactions to break the connection before the object is permanently discarded.

The basic guideline

Although I suggested several guidelines for using finalizers in my Design Techniques article, my primary guideline was:

Don't design your Java programs such that correctness depends upon "timely" finalization.

I first heard this warning against writing Java programs that depend on "timely" finalization from Tim Lindolm, who was responsible for thread and garbage-collection mechanisms of Sun's original Java virtual machine (JVM). Tim gave this warning at the JVM Birds-of-a-Feather session at the first JavaOne conference in 1996. The problem with depending on timely finalization for program correctness is that you generally don't know how garbage collection will be performed inside a JVM. Therefore, since you don't know when (or even if) any particular object will be garbage collected, you don't know when or if it will be finalized.

The upshot of garbage collection in JVMs is that you must avoid writing Java programs that will break if certain objects aren't finalized by specific times during the program's execution. If you write such a program, it may work on some JVM implementations but fail on others.

Timely finalization and nonmemory resources

A more exact way to express the same basic guideline against timely finalization is to couch it in terms of finite nonmemory resources, such as sockets or file handles.

Don't depend on finalizers to release finite nonmemory resources.

This guideline considers that the primary way in which a program could depend on timely finalization is if the finalizer is used to release or retire some finite nonmemory resource, such as a file handle, a socket, memory allocated in a native method, or a running thread. Here's the example I gave in my Design Techniques article:

An example of an object that breaks this rule [against depending on timely finalization] is one that opens a file in its constructor and closes the file in its finalize() method. Although this design seems neat, tidy, and symmetrical, it potentially creates an insidious bug. A Java program generally will have only a finite number of file handles at its disposal. When all those handles are in use, the program won't be able to open any more files.

A Java program that makes use of such an object (one that opens a file in its constructor and closes it in its finalizer) may work fine on some JVM implementations. On such implementations, finalization would occur often enough to keep a sufficient number of file handles available at all times. But the same program may fail on a different JVM whose garbage collector doesn't finalize often enough to keep the program from running out of file handles. Or, what's even more insidious, the program may work on all JVM implementations now but fail in a mission-critical situation a few years (and release cycles) down the road.

When to use finalizers

In my Design Techniques article, I concluded that finalizers are useful only rarely, but I gave a few examples of when finalizers did make sense:

One reasonable, though rare, application for a finalizer is to free memory allocated by native methods. If an object invokes a native method that allocates memory (perhaps a C function that calls malloc()), that object's finalizer could invoke a native method that frees that memory (calls free()). In this situation, you would be using the finalizer to free up memory allocated on behalf of an object -- memory that will not be automatically reclaimed by the garbage collector.

Another, more common, use of finalizers is to provide a fallback mechanism for releasing finite nonmemory resources such as file handles or sockets. As mentioned previously, you shouldn't rely on finalizers for releasing finite nonmemory resources. Instead, you should provide a method that will release the resource. But you may also wish to include a finalizer that checks to make sure the resource has already been released, and if it hasn't, that goes ahead and releases it. Such a finalizer guards against (and hopefully will not encourage) sloppy use of your class. If a client programmer forgets to invoke the method you provided to release the resource, the finalizer will release the resource if the object is ever garbage collected. The finalize() method of the LogFileManager class, shown later in this article, is an example of this kind of finalizer.

The "Finalizable Object" idiom

After ending my Design Techniques column in early 1999, I began to organize the column material into a book called The Precise Object. This book, which I am still in the process of writing, will include 10 Java object idioms. In organizing the material, I decided that using a finalizer to perform the cleanup neglected by the client code was important enough to mention as an idiom in the book. I call this idiom the "Finalizable Object" idiom.

Any class containing one or more methods that can grab a finite nonmemory resource, which isn't released before the method returns, should be finalizable. Here's the recipe for the Finalizable Object idiom:

  • Provide some mechanism for explicit cleanup, such as a close() method, that client programmers should invoke before releasing the last reference to the object. Describe the cleanup requirements in the documentation for the type in which the cleanup method(s) are declared. (It might be multiple methods, for example, because if an object fires off a thread and opens a file, the client might need to invoke stop() to stop the thread and close() to close the file.)

  • Declare a finalizer that checks to see if the client correctly performed the cleanup, and if not, performs it itself.

Testing "final conditions"

On a few occasions I've presented The Precise Object material in a workshop with Bruce Eckel. In the workshop, which I call the "Object Design Workshop," I present a guideline or idiom, then lead a discussion about it, so that participants can share their experiences and opinions and learn from one another.

Last summer, Bruce and I gave our first iteration of this workshop in Crested Butte, Colorado. During the Finalizable Object idiom discussion, Bruce expressed concern that if someone defines a finalizer that does clean up, then client programmers might become lazy about calling the cleanup method. He was worried that programmers would see the finalizer and think, "I don't need to invoke close() (or whatever the cleanup method is called), because the finalizer will invoke close() for me." Such laziness would be dangerous because the program would depend on timely finalization for correctness. Given such a dependency, the application could potentially run out of file handles on some virtual machine implementations.

Bruce and I realized that such a finalizer could detect a programming bug, which I subsequently started calling an unmet "final condition." The semantics of a finalizable object should state that clients must put the finalizable object into a "cleaned up" state, by invoking the cleanup method or methods on the object, before the client releases all references to the object.

Thus, if a finalizable object's finalizer detects that the client didn't call the cleanup method before releasing all references to the object, the finalizer has detected an unmet final condition. In effect, it has detected a programming error, because the client didn't use the object correctly, as defined by the semantics of the object's class.

Bruce and I also realized that a final condition is similar to the pre-conditions, post-conditions, and invariants of Bertrand Meyer's "Design by Contract" approach to programming (Resources). Just as unmet preconditions, postconditions, and invariants can usually be reported, we discussed that perhaps a finalizer could actually report an unmet final condition in some way, such as with an assert or a message in an error log. Bruce felt that the finalizer shouldn't fix the problem by calling the cleanup method, but should just report the problem. I continued to believe in the importance of my Finalizable Object idiom, which states that a finalizer should perform any cleanup the program neglected to perform. But nevertheless, in my discussion of the idiom I raise the possibility of reporting unmet final conditions, in addition to performing the cleanup.

What does this have to do with Jini?

Although I've been focused on Java technology since early 1996 and have written many Java programs, articles, and books, while pondering a problem associated with Jini, it finally dawned on me why finalizers really exist. This light bulb lit up in my brain a day after my second meeting with the Jini architects at Sun's Burlington, Mass. facility, where I participated in a design review of the Jini Service UI proposal. I hope this article will trigger a discussion about the issue; I have created a discussion topic at the Jini Forum at Artima.com to host that discussion.

While thinking about Jini service UIs, it occurred to me that Jini clients will often pull in, from the lookup service, objects of whose class the client has no, or only partial, prior knowledge. If such objects don't make use of any finite resources on the client besides memory, the client can just release all references to the objects, and the garbage collector will reclaim the memory. But what if such an object, upon finding itself resurrected inside the client, opens a socket back to the host from which it came? What if such an object fires off a thread? What if it, upon finding itself with sufficient permission, opens a file on the client side?

If all these activities took place in a Java application, the program would likely include some code that had sufficient prior knowledge of the object's class to enable it to invoke a cleanup method. After all, the object had to be instantiated somewhere inside the application, and instantiating an object most often involves prior knowledge of its class.

But in a mobile code scenario, such as a Jini client receiving objects from a Jini lookup service, the client code may not have any more knowledge of the object's class other than the java.lang.Object contract. The code that originally instantiated the object, which most likely has full knowledge of the object's class, is sitting somewhere else across the network, not in the client. If the code that knows how to invoke a cleanup method on the object is sitting across the network, the client code can't call the cleanup method locally, in the client's address space, where the nonmemory resources are being held. Thus, the client and Jini have a problem.

The problem arises because of an intersection of two Java and Jini features:

  • Garbage collectors release only JVM memory and don't automatically clean up anything else.
  • Jini clients may download objects about which they don't have full prior knowledge. (Classes and interfaces the client doesn't have available locally can be downloaded.)

Given that I became aware of this problem while thinking about service UIs, I decided to propose a solution in the Service UI proposal, which I brought to my meeting with the Jini architects. I invented an interface called Terminatable that declared one method, terminateService(). Here's the interface with javadoc comments describing the semantics of its one method:

package net.jini.service;

public interface Terminatable {

/** * Terminates a service and prepares its service object for garbage collection. * Once this method has been invoked on an object, the service may no longer * be usable by a client. Thus, this method should only be invoked on a * service object right before all references to the service object are * released. To use the same service again once this method has been invoked on * the service object, the client will have to download the service object * again. */ public void terminateService(); }

How to use Terminatable

Here's what I wrote in the Service UI proposal about Terminatable:

1 2 3 Page 1
Page 1 of 3