Cleanup: finalization and garbage collection

Bruce Eckel’s Thinking in Java Contents | Prev | Next

garbage
collection

Programmers
know about the importance of initialization, but often forget the importance of
cleanup. After all, who needs to clean up an
int?
But with libraries, simply “letting go” of an object once
you’re done with it is not always safe. Of course, Java has the garbage
collector to reclaim the memory of objects that are no longer used. Now
consider a very special and unusual case. Suppose your object allocates
“special” memory without using
new.
The garbage collector knows only how to release memory allocated
with
new,
so it won’t know how to release the object’s “special”
memory. To handle this case, Java provides a method called
finalize( )
that you can define for your class. Here’s how it’s
supposed
to work. When the garbage collector is ready to release the storage used for
your object, it will first call
finalize( ),
and only on the next garbage-collection pass will it reclaim the object’s
memory. So if you choose to use
finalize( ),
it gives you the ability to perform some important cleanup
at
the time of garbage collection
.

Garbage
collection is not destruction.

If
you remember this, you will stay out of trouble. What it means is that if there
is some activity that must be performed before you no longer need an object,
you must perform that activity yourself. Java has no destructor or similar
concept, so you must create an ordinary method to perform this cleanup. For
example, suppose in the process of creating your object it draws itself on the
screen. If you don’t explicitly erase its image from the screen, it might
never get cleaned up. If you put some kind of erasing functionality inside
finalize( ),
then if an object is garbage-collected, the image will first be removed from
the screen, but if it isn’t, the image will remain. So a second point to
remember is:

Your
objects might not get garbage collected.

What
is finalize( ) for?

You
might believe at this point that you should not use
finalize( )
as a general-purpose cleanup method. What good is it?

A
third point to remember is:

Garbage
collection is only about memory.

That
is, the sole reason for the existence of the garbage collector is to recover
memory that your program is no longer using. So any activity that is associated
with garbage collection, most notably your
finalize( )
method, must also be only about memory and its deallocation.

Does
this mean that if your object contains other objects
finalize( )
should explicitly release those objects? Well, no – the garbage collector
takes care of the release of all object memory regardless of how the object is
created. It turns out that the need for
finalize( )
is limited to special cases, in which your object can allocate some storage in
some way other than creating an object. But, you might observe, everything in
Java is an object so how can this be?

It
would seem that
finalize( )
is in place because of the possibility that you’ll do something C-like by
allocating memory using a mechanism other than the normal one in Java. This can
happen primarily through
native
methods
,
which are a way to call non-Java code from Java. (Native methods are discussed
in Appendix A.) C and C++ are the only languages currently supported by native
methods, but since they can call subprograms in other languages, you can
effectively call anything. Inside the non-Java code, C’s
malloc( )
family of functions might be called to allocate storage, and unless you call
free( )
that storage will not be released, causing a memory leak. Of course,
free( )
is a C and C++ function, so you’d need call it in a native method inside
your
finalize( ).

You
must perform cleanup

One
of the things
finalize( )
can be useful for is observing the process of garbage collection. The following
example shows you what’s going on and summarizes the previous
descriptions of garbage collection:

//: Garbage.java
// Demonstration of the garbage
// collector and finalization
 
class Chair {
  static boolean gcrun = false;
  static boolean f = false;
  static int created = 0;
  static int finalized = 0;
  int i;
  Chair() {
    i = ++created;
    if(created == 47)
      System.out.println("Created 47");
  }
  protected void finalize() {
    if(!gcrun) {
      gcrun = true;
      System.out.println(
        "Beginning to finalize after " +
        created + " Chairs have been created");
    }
    if(i == 47) {
      System.out.println(
        "Finalizing Chair #47, " +
        "Setting flag to stop Chair creation");
      f = true;
    }
    finalized++;
    if(finalized >= created)
      System.out.println(
        "All " + finalized + " finalized");
  }
}
 
public class Garbage {
  public static void main(String[] args) {
    if(args.length == 0) {
      System.err.println("Usage: n" +
        "java Garbage beforen  or:n" +
        "java Garbage after");
      return;
    }
    while(!Chair.f) {
      new Chair();
      new String("To take up space");
    }
    System.out.println(
      "After all Chairs have been created:n" +
      "total created = " + Chair.created +
      ", total finalized = " + Chair.finalized);
    if(args[0].equals("before")) {
      System.out.println("gc():");
      System.gc();
      System.out.println("runFinalization():");
      System.runFinalization();
    }
    System.out.println("bye!");
    if(args[0].equals("after"))
      System.runFinalizersOnExit(true);
  }
} ///:~ 

The
above program creates many
Chair
objects, and at some point after the garbage collector begins running, the
program stops creating
Chairs.
Since the garbage collector can run at any time, you don’t know exactly
when it will start up, so there’s a flag called
gcrun
to indicate whether the garbage collector has started running yet. A second flag
f
is a way for
Chair
to tell the
main( )
loop that it should stop making objects. Both of these flags are set within
finalize( ),
which is called during garbage collection.

Two
other
static
variables,
created
and
finalized,
keep track of the number of
objs
created versus the number that get finalized by the garbage collector. Finally,
each
Chair
has its own (non-
static)
int
i
so it can keep track of what number it is. When
Chair
number 47 is finalized, the flag is set to
true
to bring the process of
Chair
creation
to a stop.

All
this happens in
main( ),
in the loop

    while(!Chair.f) {
      new Chair();
      new String("To take up space");
    }

You
might wonder how this loop could ever finish, since there’s nothing
inside that changes the value of
Chair.f.
However, the
finalize( )
process will, eventually, when it finalizes number 47.

The creation of a
String
object during each iteration is simply extra garbage being created to encourage
the garbage collector to kick in, which it will do when it starts to get
nervous about the amount of memory available.

When
you run the program, you provide a command-line argument of
“before” or “after.” The “before” argument
will call the
System.gc( )
method (to force execution of the garbage collector) along with the
System.runFinalization( )
method to run the finalizers. These methods were available in Java
1.0, but the
runFinalizersOnExit( )
method that is invoked by using the “after” argument is available
only in Java 1.1
[19]
and beyond. (Note you can call this method any time during program execution,
and the execution of the finalizers is independent of whether the garbage
collector runs).

The
preceding program shows that, in Java 1.1, the promise that finalizers will
always be run holds true, but only if you explicitly force it to happen
yourself. If you use an argument that isn’t “before” or
“after” (such as “none”), then neither finalization
process will occur, and you’ll get an output like this:

Created 47
Beginning to finalize after 8694 Chairs have been created
Finalizing Chair #47, Setting flag to stop Chair creation
After all Chairs have been created:
total created = 9834, total finalized = 108
bye!

Thus,
not all finalizers get called by the time the program completes.
[20]
To force finalization to happen, you can call
System.gc( )
followed by
System.runFinalization( ).
This will destroy all the objects that are no longer in use up to that point.
The odd thing about this is that you call
gc( )
before
you call
runFinalization( ),
which seems to contradict the Sun documentation, which claims that finalizers
are run first, and then the storage is released. However, if you call
runFinalization( )
first, and then
gc( ),
the finalizers will not be executed.


[19]
Unfortunately,
the implementations of the garbage collector in Java 1.0 would never call
finalize( )
correctly. As a result,
finalize( )
methods that were essential (such as those to close a file) often didn’t
get called. The documentation claimed that all finalizers would be called at
the exit of a program, even if the garbage collector hadn’t been run on
those objects by the time the program terminated. This wasn’t true, so as
a result you couldn’t reliably expect
finalize( )
to be called for all objects. Effectively,
finalize( )
was useless in Java 1.0.

[20]
By the time you read this, some Java Virtual Machines may show different
behavior.

More by Author

Must Read