Thinking in Java - 4th Edition
1079 pág.

Thinking in Java - 4th Edition

DisciplinaProgramação Orientada A Objetos5.153 materiais78.325 seguidores
Pré-visualização50 páginas
releases the 
storage for you. So from a simplistic standpoint, you could say that because of garbage 
collection, Java has no destructor. You\u2019ll see as this book progresses, however, that the 
presence of a garbage collector does not remove the need for or the utility of destructors. 
(And you should never call finalize( ) directly, so that\u2019s not a solution.) If you want some 
kind of cleanup performed other than storage release, you must still explicitly call an 
appropriate method in Java, which is the equivalent of a C++ destructor without the 
Remember that neither garbage collection nor finalization is guaranteed. If the JVM isn\u2019t 
close to running out of memory, then it might not waste time recovering memory through 
garbage collection. 
The termination condition 
In general, you can\u2019t rely on finalize( ) being called, and you must create separate \u201ccleanup\u201d 
methods and call them explicitly. So it appears that finalize( ) is only useful for obscure 
memory cleanup that most programmers will never use. However, there is an interesting use 
of finalize( ) that does not rely on it being called every time. This is the verification of the 
termination condition4 of an object. 
At the point that you\u2019re no longer interested in an object\u2014when it\u2019s ready to be cleaned up\u2014
that object should be in a state whereby its memory can be safely released. For example, if 
the object represents an open file, that file should be closed by the programmer before the 
object is garbage collected. If any portions of the object are not properly cleaned up, then you 
have a bug in your program that can be very difficult to find. finalize( ) can be used to 
eventually discover this condition, even if it isn\u2019t always called. If one of the finalizations 
happens to reveal the bug, then you discover the problem, which is all you really care about. 
Here\u2019s a simple example of how you might use it: 
//: initialization/ 
// Using finalize() to detect an object that 
// hasn\u2019t been properly cleaned up. 
class Book { 
 boolean checkedOut = false; 
 Book(boolean checkOut) { 
 checkedOut = checkOut; 
4 A term coined by Bill Venners ( during a seminar that he and I were giving together. 
 void checkIn() { 
 checkedOut = false; 
 protected void finalize() { 
 System.out.println("Error: checked out"); 
 // Normally, you\u2019ll also do this: 
 // super.finalize(); // Call the base-class version 
public class TerminationCondition { 
 public static void main(String[] args) { 
 Book novel = new Book(true); 
 // Proper cleanup: 
 // Drop the reference, forget to clean up: 
 new Book(true); 
 // Force garbage collection & finalization: 
} /* Output: 
Error: checked out 
The termination condition is that all Book objects are supposed to be checked in before they 
are garbage collected, but in main( ), a programmer error doesn\u2019t check in one of the books. 
Without finalize( ) to verify the termination condition, this can be a difficult bug to find. 
Note that System.gc( ) is used to force finalization. But even if it isn\u2019t, it\u2019s highly probable 
that the errant Book will eventually be discovered through repeated executions of the 
program (assuming the program allocates enough storage to cause the garbage collector to 
You should generally assume that the base-class version of finalize( ) will also be doing 
something important, and call it using super, as you can see in Book.finalize( ). In this 
case, it is commented out because it requires exception handling, which we haven\u2019t covered 
Exercise 10: (2) Create a class with a finalize( ) method that prints a message. In 
main( ), create an object of your class. Explain the behavior of your program. 
Exercise 11: (4) Modify the previous exercise so that your finalize( ) will always be 
Exercise 12: (4) Create a class called Tank that can be filled and emptied, and has a 
termination condition that it must be empty when the object is cleaned up. Write a 
finalize( ) that verifies this termination condition. In main( ), test the possible scenarios 
that can occur when your Tank is used. 
How a garbage collector works 
If you come from a programming language where allocating objects on the heap is expensive, 
you may naturally assume that Java\u2019s scheme of allocating everything (except primitives) on 
the heap is also expensive. However, it turns out that the garbage collector can have a 
significant impact on increasing the speed of object creation. This might sound a bit odd at 
first\u2014that storage release affects storage allocation\u2014but it\u2019s the way some JVMs work, and it 
122 Thinking in Java Bruce Eckel 
means that allocating storage for heap objects in Java can be nearly as fast as creating storage 
on the stack in other languages. 
For example, you can think of the C++ heap as a yard where each object stakes out its own 
piece of turf. This real estate can become abandoned sometime later and must be reused. In 
some JVMs, the Java heap is quite different; it\u2019s more like a conveyor belt that moves 
forward every time you allocate a new object. This means that object storage allocation is 
remarkably rapid. The \u201cheap pointer\u201d is simply moved forward into virgin territory, so it\u2019s 
effectively the same as C++\u2019s stack allocation. (Of course, there\u2019s a little extra overhead for 
bookkeeping, but it\u2019s nothing like searching for storage.) 
You might observe that the heap isn\u2019t in fact a conveyor belt, and if you treat it that way, 
you\u2019ll start paging memory\u2014moving it on and off disk, so that you can appear to have more 
memory than you actually do. Paging significantly impacts performance. Eventually, after 
you create enough objects, you\u2019ll run out of memory. The trick is that the garbage collector 
steps in, and while it collects the garbage it compacts all the objects in the heap so that you\u2019ve 
effectively moved the \u201cheap pointer\u201d closer to the beginning of the conveyor belt and farther 
away from a page fault. The garbage collector rearranges things and makes it possible for the 
high-speed, infinite-free-heap model to be used while allocating storage. 
To understand garbage collection in Java, it\u2019s helpful learn how garbage-collection schemes 
work in other systems. A simple but slow garbage-collection technique is called reference 
counting. This means that each object contains a reference counter, and every time a 
reference is attached to that object, the reference count is increased. Every time a reference 
goes out of scope or is set to null, the reference count is decreased. Thus, managing 
reference counts is a small but constant overhead that happens throughout the lifetime of 
your program. The garbage collector moves through the entire list of objects, and when it 
finds one with a reference count of zero it releases that storage (however, reference counting 
schemes often release an object as soon as the count goes to zero). The one drawback is that 
if objects circularly refer to each other they can have nonzero reference counts while still 
being garbage. Locating such self-referential groups requires significant extra work for the 
garbage collector. Reference counting is commonly used to explain one kind of garbage 
collection, but it doesn\u2019t seem to be used in any JVM implementations. 
In faster schemes, garbage collection is not based on reference counting. Instead, it is based 
on the idea that any non-dead object must ultimately be traceable back to a reference that 
lives either on the stack or in static storage. The chain might go through several layers of 
objects. Thus, if you start in the stack and in the static storage area and walk through all the 
references, you\u2019ll find all the live objects. For each reference that you find, you must trace