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 .

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

//: 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 before\n  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.



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • The explosion in mobile devices and applications has generated a great deal of interest in APIs. Today's businesses are under increased pressure to make it easy to build apps, supply tools to help developers work more quickly, and deploy operational analytics so they can track users, developers, application performance, and more. Apigee Edge provides comprehensive API delivery tools and both operational and business-level analytics in an integrated platform. It is available as on-premise software or through …

  • A modern mobile IT strategy is no longer an option, it is an absolute business necessity. Today's most productive employees are not tied to a desk, an office, or a location. They are mobile. And your company's IT strategy has to be ready to support them with easy, reliable, 24/7 access to the business information they need, from anywhere in the world, across a broad range of communication devices. Here's how some of the nation's most progressive corporations are meeting the many needs of their mobile workers …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds