Team LiB
Previous Section Next Section

3.4. Destroying and Finalizing Objects

Now that we've seen how new objects are created and initialized in Java, we need to study the other end of the object life cycle and examine how objects are finalized and destroyed. Finalization is the opposite of initialization.

In Java, the memory occupied by an object is automatically reclaimed when the object is no longer needed. This is done through a process known as garbage collection. Garbage collection is a technique that has been around for years in languages such as Lisp. It takes some getting used to for programmers accustomed to such languages as C and C++, in which you must call the free() function or the delete operator to reclaim memory. The fact that you don't need to remember to destroy every object you create is one of the features that makes Java a pleasant language to work with. It is also one of the features that makes programs written in Java less prone to bugs than those written in languages that don't support automatic garbage collection.

3.4.1. Garbage Collection

The Java interpreter knows exactly what objects and arrays it has allocated. It can also figure out which local variables refer to which objects and arrays and which objects and arrays refer to which other objects and arrays. Thus, the interpreter is able to determine when an allocated object is no longer referred to by any other active object or variable. When the interpreter finds such an object, it knows it can safely reclaim the object's memory and does so. The garbage collector can also detect and destroy cycles of objects that refer to each other, but are not referenced by any other active objects. Any such cycles are also reclaimed.

Different VM implementations handle garbage collection in different ways. It is reasonable, however, to imagine the garbage collector running as a low-priority background thread, so it does most of its work when nothing else is going on, such as during idle time while waiting for user input. The only time the garbage collector must run while something high-priority is going on (i.e., the only time it actually slows down the system) is when available memory has become dangerously low. This doesn't happen very often because the low-priority thread cleans things up in the background.

3.4.2. Memory Leaks in Java

The fact that Java supports garbage collection dramatically reduces the incidence of a class of bugs known as memory leaks. A memory leak occurs when memory is allocated and never reclaimed. At first glance, it might seem that garbage collection prevents all memory leaks because it reclaims all unused objects. A memory leak can still occur in Java, however, if a valid (but unused) reference to an unused object is left hanging around. For example, when a method runs for a long time (or forever), the local variables in that method can retain object references much longer than they are actually required. The following code illustrates:

public static void main(String args[]) {
  int big_array[] = new int[100000];

  // Do some computations with big_array and get a result. 
  int result = compute(big_array);

  // We no longer need big_array. It will get garbage collected when there
  // are no more references to it. Since big_array is a local variable,
  // it refers to the array until this method returns. But this method
  // doesn't return. So we've got to explicitly get rid of the reference
  // ourselves, so the garbage collector knows it can reclaim the array. 
  big_array = null;

  // Loop forever, handling the user's input
  for(;;) handle_input(result);
}

Memory leaks can also occur when you use a hash table or similar data structure to associate one object with another. Even when neither object is required anymore, the association remains in the hash table, preventing the objects from being reclaimed until the hash table itself is reclaimed. If the hash table has a substantially longer lifetime than the objects it holds, this can cause memory leaks.

The key to avoiding memory leaks is to set object references to null when they are no longer needed if the object that contains those references is going to continue to exist. One common source of leaks is in data structures in which an Object array is used to represent a collection of objects. It is common to use a separate size field to keep track of which elements of the array are currently valid. When removing an object from the collection, it is not sufficient to simply decrement this size field: you must also set the appropriate array element to null so that the obsolete object reference does not live on.

3.4.3. Object Finalization

A finalizer in Java is the opposite of a constructor. While a constructor method performs initialization for an object, a finalizer method can be used to perform cleanup or "finalization" for the object. Garbage collection automatically frees up the memory resources used by objects, but objects can hold other kinds of resources, such as open files and network connections. The garbage collector cannot free these resources for you, so you may occasionally want to write a finalizer method for any object that needs to perform such tasks as closing files, terminating network connections, deleting temporary files, and so on. This is particularly true for classes that use native methods: these classes may need a native finalizer to release native resources (including memory) that are not under the control of the Java garbage collector.

A finalizer is an instance method that takes no arguments and returns no value. There can be only one finalizer per class, and it must be named finalize().[3] A finalizer can throw any kind of exception or error, but when a finalizer is automatically invoked by the garbage collector, any exception or error it throws is ignored and serves only to cause the finalizer method to return. Finalizer methods are typically declared protected (which we have not discussed yet) but can also be declared public. An example finalizer looks like this:

[3] C++ programmers should note that although Java constructor methods are named like C++ constructors, Java finalization methods are not named like C++ destructor methods. As we will see, they do not behave quite like C++ destructor methods either.

protected void finalize() throws Throwable {
  // Invoke the finalizer of our superclass
  // We haven't discussed superclasses or this syntax yet
  super.finalize();

  // Delete a temporary file we were using
  // If the file doesn't exist or tempfile is null, this can throw
  // an exception, but that exception is ignored. 
  tempfile.delete();
}

Here are some important points about finalizers:

  • If an object has a finalizer, the finalizer method is invoked sometime after the object becomes unused (or unreachable), but before the garbage collector reclaims the object.

  • Java makes no guarantees about when garbage collection will occur or in what order objects will be collected. Therefore, Java can make no guarantees about when (or even whether) a finalizer will be invoked, in what order finalizers will be invoked, or what thread will execute finalizers.

  • The Java interpreter can exit without garbage collecting all outstanding objects, so some finalizers may never be invoked. In this case, resources such as network connections are closed and reclaimed by the operating system. Note, however, that if a finalizer that deletes a file does not run, that file will not be deleted by the operating system.

  • To ensure that certain actions are taken before the VM exits, Java 1.1 provided the Runtime method runFinalizersOnExit(). Unfortunately, however, this method can cause deadlock and is inherently unsafe; it was deprecated in 1.2. In Java 1.3 and later, the Runtime method addShutdownHook() can safely execute arbitrary code before the Java interpreter exits.

  • After a finalizer is invoked, objects are not freed right away. This is because a finalizer method can resurrect an object by storing the this pointer somewhere so that the object once again has references. Thus, after finalize() is called, the garbage collector must once again determine that the object is unreferenced before it can garbage-collect it. However, even if an object is resurrected, the finalizer method is never invoked more than once. Resurrecting an object is never a useful thing to dojust a strange quirk of object finalization.

  • The finalize( ) method is an instance method, and finalizers act on instances. There is no equivalent mechanism for finalizing a class.

In practice, it is quite rare for an application-level class to require a finalize( ) method. Finalizer methods are more useful, however, when writing Java classes that interface to native platform code with native methods. In this case, the native implementation can allocate memory or other resources that are not under the control of the Java garbage collector and need to be reclaimed explicitly by a native finalize() method.

Furthermore, because of the uncertainty about when and whether a finalizer runs, it is best to avoid dependence on finalizers. For example, a class that includes a reference to a network socket should define a public close() method, which calls the close( ) method of the socket. This way, when the user of your class is done with it, she can call close( ) and be sure that the network connection is closed. You might, however, define a finalize( ) method as backup in case the user of your class forgets to call close( ) and allows an unclosed instance to be garbage-collected.

    Team LiB
    Previous Section Next Section