[prev] [up] [next]

Object Finalization

Introduction

Normally, the runtime system of Smalltalk/X (and of all other Smalltalk implementations, of course) performs garbage collection on its own. There is no need for the programmer to free objects; typically, the program does not even get any notification when objects are freed.
Objects which are no longer reachable by any other reachable object are automatically returned to the free memory pool (see ``garbage collection'').
However, for some classes, cleanup actions are needed whenever an instance dies.
Examples are: In general, all objects which somehow represent or refer to external resources (i.e. operatingSystem or shared-object) need some cleanup action to release that external resource. This can be done either manually by the programmer or automatically by the runtime system.

For the programmer, it is much easier (and less error prone) to use the finalization mechanism, instead of doing the housekeeping herself. Just think of a bitmap image with a local colormap; how would you know who else in the system uses those colors and when they can be freed without danger ?

To support object finalization, Smalltalk/X includes the WeakArray class, which provides a low level mechanism and Registry which offers a more programmer friendly higher level interface.

WeakArray: the low level mechanism

Instances of WeakArray are arrays which do not prevent their elements from being garbage collected. This means that the reference from the WeakArray to an object does not count as a reference with respect to garbage collection. In other words: the garbage collector will consider an object as being free if either no references or only references from WeakArrays to that object exist. Whenever such an object is found, the object is returned to the free memory pool as usual. and the entry in the WeakArray is set to 0 (zero). Finally, the garbage collector will inform all dependents of the WeakArray about the change.

Using this basic mechanism for object finalization is straightforward:

keep the relevant information (such as fileDescriptor, window handle etc.) in another array,
and references to the corresponding smalltalk object in a WeakArray.
Whenever the WeakArray informs its dependents about some object being reclaimed, scan it (in your #update:with:from method) for zero entries and free the corresponding resource as stored in the other array (i.e. close the file, free the window handle etc.).
(Notice, that a HandleRegistry is better suited for this concrete application.)

Another use of WeakArrays is with caching; for example, you can keep some recently used data in a WeakArray, reusing it if present.
The cache will be automatically 'flushed' by the garbage collector in regular intervals. (a concrete example is found in the Method » source method, which keeps the recently used sourcefile open for a while.)

Registry and HandleRegistry: programmer-friendly mechanisms

Since WeakArrays are a bit complicated to use, two other classes called Registry and HandleRegistry are provided.
These internally perform the above operations to keep track of reclaimed objects; thus you don't have to care for all those details. However, a registry keeps executors (which are shallow copies of the original objects) in the other array, and sends a notification to this copy.
A handleRegistry associates a handle with an object (as described above) and sends a change notification to its dependents, passing the original handle as change parameter.

Registry

Registries are perfect if some object knows by itself how to perform cleanup; the programmer can code as if the original object received the destroy notification and program as follows:
The object which wants to do some finalization actions should be registered in a registry, and will later (at finalization time) receive a #finalize-message.
In this method, perform cleanup as required. For example, close an underlying file by doing "self closeFile".
Technically, we cannot send messages to the original object, since that object is already gone at that time. Instead, the executor created by the registry will receive the #finalize message. Since the executor has the same contents as the original (especially: fileDescriptors and window handles), its #finalize method can be written as if it was the original.

Lets summarize what is needed for proper finalization:

You should keep in mind, that the executor receiving the #finalize message is a shallow copy, not the original (that has already been garbage collected!). Although in most cases, this is transparent, there are situations where it does make a difference: when keeping objects in identitySets or identityDictionaries, which compare for identity instead of equality. The executor will of course never be identical, but can be made equal to the original.

Since creating a shallow copy may be expensive (especially for big or heavily used objects), the copy is created using a slighly different copy method: #executor.
The default implementation for this (in Object) simply sends #shallowCopy.
You may want to redefine this method in your class - especially if you do not need all instance variables at finalization time.
For example, the Cursor classes #finalize method will only need the cursors resource-id - therefore, a special #executor has been defined, which creates a lightweight copy with only the id being copied.

Classes with many instance variables may avoid to create big finalization objects by returning a small placeHolder object (handle-holder), which does not use up as much memory as the original

HandleRegistry

HandleRegistries are very useful if some other object wants to keep track of living objects and perform cleanup actions, which the other objects cannot do itself.
A typical example is a communication system, which handles an IPC connection for multiple lightweight processes, and wants to be informed if any of them dies, to close the corresponding connection.
In this setup, a HandleRegistry would assign an IPC-channel ID for each process and inform the watcher if any process dies. (an alternaitive is to define a subclass of process, which does the cleanup in its finalization method - but that seems to be an ugly kludge). To use a handleRegistry, the following steps are required:

See example uses in Form, Cursor, Color, Font or ExternalStream for how registries are used, and the implementation of Registry for more protocol information.
In addition, you will find more examples in the file: "doc/coding/Registry-examples.st".


Copyright © 1995 Claus Gittinger, all rights reserved

<cg at exept.de>

Doc $Revision: 1.22 $ $Date: 2021/03/13 18:24:51 $