Cleaning Up After Jini Services

Standardize the Way Jini Clients Release Resources

by Bill Venners
March 15, 2000

First published in JavaWorld, March 2000
Summary
In this article, I look at a fundamental issue with network-mobile code: clients (such as Jini browsers) that load objects of which they have no prior knowledge cannot invoke cleanup methods on those objects. I propose a solution to this problem, using the finalize() method declared in class Object.

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:

Terminatable is an interface implemented by service objects that need to prepare themselves for garbage collection.

Service objects may be pulled into Jini client programs that were written by programmers who did not have (or did not use) full prior knowledge about the interface of the service object. Eventually, such clients may wish to offer up such service objects to their garbage collector by releasing all references to the service object.

If a service object, during the course of its lifetime on the client, creates and holds onto no finite nonmemory resources (such as file handles or sockets) and starts no threads, the client can safely release all references to the service. The garbage collector will, at its leisure, reclaim the memory occupied by the service object[...] But if a service object opens a socket connection back to the host from which it came, or opens a file locally on the client, or fires off a thread, then the client can't just let go of all references to the service object. The client must first "terminate" the service -- tell the service to prepare itself for garbage collection.

Any service object that needs to be prepared for garbage collection for any reason should implement Terminatable. Client programs, before they release the last reference to a service object, should check to see if it implements Terminatable. If so, it should invoke terminateService() on the service object before releasing the last reference to the object.

If a service object need not be prepared for garbage collection, it should not implement Terminatable. Thus, Terminatable should never be mentioned in Jini Service APIs. Terminatable should be implemented by individual service object implementation classes, if their instances need to be prepared for garbage collection.

To be client-friendly, service objects should not "start" (for example, fire off threads, allocate big chunks of memory, open a socket connection, and so forth) when they are deserialized into the client's address space. Services should in general use a lazy starting approach, in which they don't start until they are first used. If a client never uses a particular service object, then it won't ever start. This in turn implies that if a client never used a Terminatable service object, then that client wouldn't actually need to invoke terminateService() on the object before releasing the last reference to the object. However, given the possibility that some service objects may decide to ignore the rules of good citizenship and start when they are deserialized, client programs should always invoke terminateService() on Terminatable service objects before releasing the last reference.

In a different part of the Service UI proposal, I described the behavior of clients with regard to Terminatable:

If the client is finished with the service object, it can also release all references to the service object. Before releasing the last reference to the service object, however, a client should check to see whether the service object's class implements net.jini.service.Terminatable. If so, the client should invoke terminateService on the service object before releasing the last reference.

Once terminateService() has been invoked on a Terminatable service, the service object should be considered defunct. [...] Thus, if a client wants to grant a user access to the same service again after invoking terminateService() on the service object, or even just use the service directly, the client program will have to retrieve a fresh copy of the service item from the lookup service. (Put another way, terminateService() should only be called on a service object just before releasing all references to the service object.)

Concerns about Terminatable
At the design review, I mentioned how a Jini client could ensure that a network-mobile object released any nonmemory resources it held at the client, before the client released the last reference to the object. When I presented the Terminatable interface, the Jini architects didn't like it. Their reaction didn't surprise me. I brought up the issue primarily because I wanted to hear the architects' discussion about it, a discussion I found quite interesting.

First, the Jini architects believed an interface such as Terminatable didn't belong in the Service UI API, because cleaning up nonmemory resources used by network-mobile objects wasn't a problem specific to Jini Service UIs. In fact, as Jim Waldo said, "Resource reclamation is not even a problem specific to Jini programs, it's a general problem of Java programs." As a result, they concluded the Jini Service API was not the proper place to address the issue. Terminatable was given a thumbs down.

What I also found interesting, however, was that I heard no consensus on another potential solution. Various ideas were tossed about, including having finite nonmemory resources leased on the client side, or extending the garbage collector's contract so that it would promise to run if file handles or sockets ran out. But in the end, it was merely decided that since the solution was unclear, we shouldn't attempt to define something like Terminatable in the Service UI API.

John McClain, a member of Sun's Jini team and participant in the ServiceUI design review, summarized this portion of the design review this way:

Resource recovery is a hard problem. To come up with a good solution would require both many people-hours, much calendar time, and probably require changes to software and specifications the Jini team does not control (for example, the Java spec and/or the JDK). Providing a partial solution could well be worse than providing no solution at all, especially since in many circumstances there are existing ways to avoid the problems Terminatable addresses. Given a choice between holding up the service UI spec to work up a solution (a task that may well take years), providing a partial solution, and doing nothing, it seemed that discretion was the better part of valor.

One concern I had about Terminatable that the Jini architects did not bring up was the "defunct" state that invoking terminateService() could create. I mentioned the defunct state in my description of the interface contract:

Once terminateService() has been invoked on a Terminatable service, the service object should be considered defunct.

My concern was that this defunct state could render invalid other contracts offered by other interfaces and classes that make up the object. This behavior violates the principle that all the supertypes of an object's class should be compatible with each other. For example, imagine you have an object whose class implements Laughable and Toastable. Laughable declares two methods, laugh() and lastLaugh(). Toastable declares one method, toast(). You should be able to pass this object to a method that expects a Toastable, and that code should be able to operate on the object knowing only about Toastable.

Now imagine that invoking laugh() interfered with the object's ability to fulfill the Toastable contract when toast() is invoked. And that invoking lastLaugh() would enable the object to once again fulfill the Toastable contract. In effect, invoking laugh() places the object in an "untoastable" state, and invoking lastLaugh() places the object back into a "toastable" state. Given this behavior, code that knows only about Toastable would only be able to use the object as a Toastable when the object happened to be in a "toastable" state. Another way to look at this is: if the object isn't always toastable, its class really shouldn't implement Toastable. Thus, Laughable and Toastable aren't compatible interfaces, and both shouldn't be implemented by the same class.

Now consider the following language from my description of Terminatable's contract:

If a service object need not be prepared for garbage collection, it should not implement Terminatable. Thus, Terminatable should never be mentioned in Jini Service APIs. Terminatable should be implemented by individual service object implementation classes, if their instances need to be prepared for garbage collection.

Thus, terminateService() is a bit like the laugh() method of the previous example, in that laugh() was not part of the contract of Toastable, but laugh() nevertheless affected the object's ability to fulfill the Toastable contract. As soon as you invoke terminateService on a Terminatable object, it can break the contracts of all the other classes and interfaces that make up the object. Invoking terminateService() puts an object in a "defunct" state, and the rest of the object is guaranteed to work as advertised only if it's in the "nondefunct" state. In my mind, this feature of Terminatable makes it incompatible with most other interfaces and classes, so I was very uneasy about actually adopting Terminatable as the standard way to deal with mobile objects holding onto finite nonmemory resources at the client.

An epiphany about finalize()
When I was dreaming up Terminatable, for a while I entertained the possibility that the client could invoke finalize() on network-mobile objects, and that the finalizer would be responsible for cleaning up any nonmemory resources held by the client. But I discarded that idea, because I felt the semantics of finalize() were not broad enough to encompass the clients' explicit invocation of the method. After all, isn't the finalize() method supposed to be invoked not by the application, but by the garbage collector?

However, after I returned from my second meeting with the Jini architects, it occurred to me that the contract of finalize() includes the concept of a defunct state. Once finalize() is invoked, the object is defunct, and therefore may not fulfill any or all of its contracts. And -- what constituted the big light bulb popping on -- finalize() is declared in class Object. Given that every object inherits finalize(), every object inherits the concept of a defunct state entered after finalize() is invoked.

At that point I regretted bringing Terminatable to the Jini architects and wished I'd got them talking about using finalize() in this way. Instead, however, I wrote this article, and hope to get you to participate in the discussion. Here's my proposal:

Jini service objects, or any mobile code (such as attributes in Entrys), that uses finite nonmemory resources of the client should be "finalizable objects," as defined by the Finalizable Object idiom, and their finalize() method should be public. Given a public finalize() method, clients with no prior knowledge of any cleanup methods offered by an object could instead invoke finalize() on the object before releasing the last reference to it.

This approach would standardize the way programmers of network-mobile objects provide a method that lets any client prepare those objects for garbage collection, even if the client has absolutely no prior knowledge about the objects other than the java.lang.Object contract.

Relying on the kindness of strangers
One last comment I'd like to make concerns a related issue that has weighed heavily on my pro-network-mobile-code mind. I am concerned about the extent to which client programmers have to rely on the kindness of strangers who write the code that clients load when they pull in objects across the network. I think it's important that a standard way to deal with finite nonmemory resources be established, which this article proposes. But even if this proposal were to be adopted, clients would still be open to unintentional denial-of-service bugs. If an object opened a socket connection but didn't declare a finalize method that ensured the socket was closed, what's the client to do?

In my opinion, if the Java Platform is going to be seriously used as a landing pad for network mobile code and objects, virtual machines must give clients a way to "kill" mobile code, perhaps all code loaded into a namespace, similar to the way a kill signal can be sent to a process on Unix. Perhaps the answer is to place code loaded from different codebases into different virtual machine instances. This approach would place code from different codebases into different processes, which could then be killed. Any threads, open file handles, open sockets, and so forth could be eliminated along with the process.

I have spent many years being frustrated at how easily and often Microsoft Windows tends to crash. I have always suspected that the ease with which a Windows program can interfere with everything around it was, in great part, the fault of a poorly designed operating system. A well-designed operating system, I figured, would not allow programs to crash the operating system.

And now, I'm concerned that the Java Platform, when used as a landing pad for untrusted network-mobile code, may be offering that code too many opportunities for denial-of-service bugs. To the extent that writing a well-behaved mobile object is difficult and complex, bugs will happen. Would users be as frustrated using a Jini client, which poorly written Jini services can cause to freeze up, as they would using Windows? To what extent should Jini client programmers, and the users of those clients, be forced to rely on the kindness and programming ability of strangers who write the Jini services that the clients pull into their virtual machines?

Conclusion
In addition to simply describing Jini technology in this column, I try to ask questions to stimulate thinking and discussion in the Jini community about various technical topics.

I believe it's important to resolve the issue highlighted in this article, that of how clients of network mobile objects can ensure that finite nonmemory resources held by those objects are released in a timely manner. I have proposed one approach: network mobile objects could expose a public finalizer that clients could invoke before releasing the last reference to the objects. But I think the most important role I can serve is to stimulate a discussion about this issue.

What's your opinion about cleaning up after network mobile objects? To discuss the material presented in this article, visit the discussion forum at:

http://www.artima.com/jini/jf/finalize/index.html

Resources

This article was first published under the name Cleaning up after Jini services in JavaWorld, a division of Web Publishing, Inc., March 2000.

Talk back!

Have an opinion? Be the first to post a comment about this article.

About the author

Bill Venners has been writing software professionally for 14 years. Based in Silicon Valley, he provides software consulting and training services and maintains a Web site for Java and Jini developers, artima.com. He is author of the book: Inside the Java Virtual Machine, published by McGraw-Hill.