Navigation:  Programming Cookbook > Weak References and Finalization >

Zen and the art of Finalization

Previous pageReturn to chapter overviewNext page

With the capability to do object finalization, there is a a choice to make about when to use it. If anything, the temptation will be to overuse it, especially since the expression ObjectWhichParpsOnDeath new beFinalizable can provide almost instant gratification.

Those of us with a C++ background might be tempted into confusing finalization, releasing, destructors and deleting, so here is a quote from Stroustrup to clarify things:

'When implementing a garbage collection scheme one must decide whether to invoke the destructor for a collected object or not. Deciding which is the right thing to do is not easy. In [The C++ Programming Language, 2nd Edition] I wrote:

"Garbage collection can be seen as a way of simulating an infinite memory in a limited memory. With this in mind, we can answer the common question: Should a garbage collector call the destructor for an object it recycles? The answer is no, because an object placed on free store and never deleted is never destroyed. Seen in this light using delete is simply a way of requesting the destructor to be called (together with a notification to the system that the object's memory may be recycled). But what if we actually do want an action performed for an object allocated on the free store but never deleted? Note that this problem does not arise for static and automatic objects; their destructors are always called implicitly. Note also that actions performed "at garbage-collection time" are unpredictable because they may happen at any time between the last use of the object and "the end of the program." This implies that the state of the program at the time of their execution is unknown. This again makes such actions hard to program correctly and less useful than is sometimes imagined.

Where such actions are needed, the problem of performing an action at some unspecified "destruction time" can be solved by providing a registration server. An object that needs a service performed "at the end of the program" places its address and a pointer to a "cleanup" function in a global associative array."

I am now less certain. This model would certainly work, but maybe having the garbage collector call the destructors would be sufficiently simple to use to be worthwhile. That depends on exactly what objects are collected and what actions their destructors perform. This is a question that can't be decided by armchair philosophy, and there doesn't seem to be much relevant experience from other languages. Unfortunately, it is also a problem for which it is hard to conduct real experiments."

Byarne Stroustrup, The Design and Evolution of C++, 1994

So, Stroustrup points out that actually freeing the memory occupied by an object and object destruction are distinct things, and he cannot decide whether we should implicitly perform the latter when we automatically perform the former (i.e. garbage collection). Of course, Dolphin has superior garbage collection facilities to C++, and there are two forms of "destructor".

In C++ it is possible to both call operator delete against an object, AND invoke its destructor directly (with the former implicitly doing the latter). In Smalltalk one doesn't have the capability to do the former - one cannot (thankfully) force an object to disappear. Objects stubbornly continue to exist while there are outstanding references to them from other objects, and as one may not be the "owner" of all the sources of reference (and one cannot necessarily predict who will want to reference one's object), one can't always make an object disappear just by nilling the references one knows about. The nearest equivalent, though only by convention, is explicitly sending an Object the a #release or #free message - a kind of notification that one expects it to disappear, and want it to release any resources it is holding.

Sending #free (the selector is only a convention, it has no special significance) is similar to directly invoking a destructor. It can be error prone to send a release message to an object, though not as error prone as deleting a pointer which some other C++ object is referencing, because, depending on the implementation, it may put the object into an invalid state. Release messages should not be sent to objects from objects which do not "own" them, unless the release method leaves the object in an valid state. In general, try to have release methods which leave the receiver in a valid state, perhaps by using lazily initializing accessor methods.

In Smalltalk all objects are potentially garbage collectable, and one cannot predict when this will occur, or what state other objects in the system will be when this happens. Therefore #finalize can be safely used to do local destruction, but should not, in general, perform the kind of actions that release methods are likely to do (like removing the object from certain collections etc.), because one does not necessarily know what state those other objects are in. Indeed, in order for an object to be finalized, it must have no outstanding references, and even weak object references will have been changed to the corpse object by the VM before an object receives a #finalize message. It is for this reason that the default implementation of #finalize in Object does not send #free. #finalize may be received by an object marked as finalizable some unspecified and unpredictable time after it becomes ready to be finalized. Indeed it is possible that an object may never get finalized, if, for example, there are processor hogging processes running at priorities above the system background priority and the finalization queue does not reach its high water mark (if this is likely to be the case, arrange to send #administerLastRites to the memory manager from a higher priority process at regular intervals).

In summary:

Use #free to perform "global" clean up, and either be prepared to receiver further messages after a #free, or document that an object's behaviour is undefined after a #free (see also the Object Liberation Strategy pattern).
Use #finalize to perform destruction local to the objects implementation, e.g. to destroy an external resource.
When an object receives #finalize it can assume that no other objects are interested in it, and that it need not maintain a valid state because it is about to die (however, it is possible to give an object a reprieve in its #finalize method).
Should other objects require notification that an object is being finalized, either make them weakly reference the object (and be prepared for the asynchronous appearance of corpses - the hard option), or register as a dependent of the object, and have it send out an update message from its #finalize (the easier option). You may want to use local storage of the dependents collection in this case by implementing the #getDependents: and #setDependents: protocol.
Do not overuse finalization - it is quite expensive, it is asynchronous, and it is not the way to maintain collections (use Weak Collections instead). Most Smalltalk objects do not require finalization - only those that hold resources in addition to their memory.