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.
As I mentioned in Chapter 2, "Java Syntax from the Ground Up", the memory occupied by an object is automatically reclaimed when the object is no longer needed. This is done through a process known as garbagecollection. Garbage collection is not some newfangled technique; it has been around for years in languages such as Lisp. It just takes some getting used to for programmers accustomed to such languages as C and C++, in which you must call the free() method 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.
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 object or variable. When the interpreter finds such an object, it knows it can destroy the object safely 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.
The Java garbage collector runs as a low-priority 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 will actually slow 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.
This scheme may sound slow and wasteful of memory. Actually though, modern garbage collectors can be surprisingly efficient. Garbage collection will never be as efficient as well-written, explicit memory allocation and deallocation. But it does make programming a lot easier and less prone to bugs. And for most real-world programs, rapid development, lack of bugs, and easy maintenance are more important features than raw speed or memory efficiency.
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 argso[]) { 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 hashtable or similar data structure to associate one object with another. Even when neither object is required anymore, the association remains in the hashtable, preventing the objects from being reclaimed until the hashtable itself is reclaimed. If the hashtable has a substantially longer lifetime than the objects it holds, this can cause memory leaks.
A finalizer in Java is the opposite of a constructor. While a constructor method performs initialization for an object, a finalizer method performs 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 need 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.
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().[1] 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:
[1]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, though, any outstanding resources are usually freed by the operating system. In Java 1.1, the Runtime method runFinalizersOnExit() can force the virtual machine to run finalizers before exiting. Unfortunately, however, this method can cause deadlock and is inherently unsafe; it has been deprecated as of Java 1.2. In Java 1.3, 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 do--just a strange quirk of object finalization. As of Java 1.2, the java.lang.ref.PhantomReference class can implement an alternative to finalization that does not allow resurrection.
In practice, it is relatively 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 nativefinalize() method.
While Java supports both class and instance initialization through static initializers and constructors, it provides only a facility for instance finalization. The original Java specification called for a classFinalize() method that could finalize a class when the class itself became unused and was unloaded from the VM. This feature was never implemented, however, and because it has proved to be unnecessary, class finalization has been removed from the language specification.
Copyright © 2001 O'Reilly & Associates. All rights reserved.