USING FINALLY VERSUS FINALIZE TO
GUARANTEE QUICK RESOURCE
CLEANUP
The Java(tm) programming language includes a finalize method that
allows an object to free system resources, in other words, to
clean up after itself. However using finalize doesn't guarantee
that a class will clean up resources expediently. A better
approach for cleaning up resources involves the finally method
and an explicit close statement.
To compare the two approaches, let's look at an example.
public class Finalize1 {
private static final int testIter = 100;
static public void main(String[] args) {
int n;
file://Initialize a batch of objects that use finally to clean up
for (n=testIter; --n>=0;) {
UsesFinalize uf = new UsesFinalize();
}
file://Initialize a batch of objects that use an explicit close
file://to clean up. Note that the code is more complex. This
file://is a necessary evil.
for (n=testIter; --n>=0;) {
UsesClose uf = null;
try {
uf = new UsesClose();
}
finally {
if (uf != null)
uf.close();
}
}
System.out.println("This demo demonstrates the danger of relying \
on finally to expediently close resources.");
System.out.println("Testing with 100 resources:");
file://Each of the classes tracking the maximum number of "open" resources
file://at any given time.
System.out.println("Using finally to close resources required "
+ UsesFinalize.maxActive +
" open resources.");
System.out.println("Using explicit close required "
+ UsesClose.maxActive +
" open resource.");
}
static public class UsesFinalize {
static int active;
static int maxActive;
UsesFinalize() {
active++;
maxActive = Math.max(active, maxActive);
}
public void finalize() {
active--;
}
}
static public class UsesClose {
static int active;
static int maxActive;
public UsesClose() {
active++;
maxActive = Math.max(active, maxActive);
}
public void close() {
active--;
}
}
}
The Finalize1 program takes two alternative approaches to cleaning
up resources. In the first approach it creates 100 objects,
incrementing a counter for each object. It then uses the finalize
method to clean up each object. Each time it cleans up an object,
it decrements the counter.
In the second approach, 100 objects are also created. However
here the finally statement and an explicit close method are used
to clean up and decrement the counter.
If you run Finalize1, you'll see that the Finalize approach does
not decrement the counter. None of the objects are closed. However
the Finally-plus-close approach does the job it's intended to do.
It decrements the counter for each object. It closes all the
objects.
The purpose of the Finalize method is often misunderstood by
programmers. The Javadoc comment for Finalize states that it's
called by the garbage collector on an object when the garbage
collector determines that there are no more references to the
object. Presumably the garbage collector will, like its civil
servant namesake, visit the heap on a regular basis to clean up
resources that are no longer in use.
As reasonable as it may seem, this interpretation of finalization
relies on assumptions about garbage collection that are not
supported by the Java language specification. The primary purpose
of Java garbage collection is not to run finalizers. Garbage
collection exists to prevent programmers from calling delete. This
is a wonderful feature. For example, if you can't call delete,
then you can't accidentally call delete twice on the same object.
However, removing delete from the language is not the same thing
as automatically cleaning up. The name "garbage collection"
promises too much. Confusion might have been saved by using the
name "delete prevention" instead. In fact, the Java garbage
collection specification imposes only minimal requirements for the
behavior of garbage collection, which include:
1. Garbage collection might not ever run. If garbage collection
runs at all, and an object is no longer referenced, then that
object's finalize will run.
2. Across multiple objects, finalize order is not predictable.
Either of these rules, taken alone, would be enough to make
finalize a risky way to clean up resources. So why do developers
leap to the wrong conclusion and rely on finalize? There are two
reasons: they are swayed by the analogy to the C++ destructor, and
they often get away with it in the short run. Combine a simple
project with a better-than-average VM implementation, and finalize
will appear almost as reliable as a C++ destructor. Don't be
fooled by this temporary good luck. If you rely on finalize, your
code will not scale to larger projects, and it will not run
consistently on different virtual machines.
The correct approach to resource cleanup in Java language programs
does not rely on finalize. Instead, you simply write explicit close
methods for objects that wrap native resources. If you take this
approach, you must document that the close method exists and when
it should be called. Callers of the object must then remember to
call close when they are finished with a resource.
This probably does not live up to your hopes for garbage
collection, since you are back to the manual labor of freeing
resources yourself. Moreover, this code still has a problem: if an
exception is thrown from somewhere inside the method that calls
close, the close method will never be reached. This calls for a way
to force a code block to be executed, regardless of exceptions.
Java's finally clause fits the bill perfectly. After any try block
in Java, you can specify a finally block which will execute, no
matter how the try block exits--either normally or exceptionally.
|