- it is called *before* an object gets gc'ed, not after, and - it is *not* guaranteed to be called at all, as an object may not get gc'ed
Therefore it's not really like a destructor.
The soul is dyed the color of its thoughts. Think only on those things that are in line with your principles and can bear the light of day. The content of your character is your choice. Day by day, what you do is who you become. Your integrity is your destiny - it is the light that guides your way. - Heraclitus
The process of gc'ing an object is the destruction of that object and the reclamation of all of the memory used by said object.
A C++-style destructor, in fact, does more than simply destroy the object by freeing the memory used by it (though it does indeed do that). It provides a place for developers to free other resources that may be held open by the object, such as file handles in the operating system. Since destruction of objects in Java is automated by the gc, there is no opportunity for developers to run this kind of code as part of the destruction process itself.
Or, rather, there would be no way if not for the finalize() method. finalize() is a method provided on java.lang.Object, the granddaddy of all objects, and may be overridden for precisely this purpose. Anything provided in the body of the finalize() method is carried out before gc because the garbage collector runs it before destroying the object. Usually, though, it is advisable to avoid using the finalize() method because there are some subtleties and performance problems with doing so.
Performance-wise, finalize() is a bad idea because of the way most JVMs work. The gc organizes objects in memory into groupings based on how many references there are to those objects. Whenever the gc runs it analyzes those groupings and updates them according to what's happened since the last gc pass. Typically, even after the last reference to an object disappears, it takes several passes for the gc to recognize that and move it to the "ready for collection" grouping. Just before collection, the gc checks to see if there's a finalize() method present, and if so it runs it...but at that moment, all bets are off. The gc knows that the finalize() method itself may have caused several references to the object to be handed out all over town and now the object is no longer valid for gc, so it goes to the back of the line again. Finalizers are guaranteed only to be run once (I believe), so the next time it makes its way up to the head of the line, the gc will consider it as not having a finalize() method and it will get collected this time.
You can see, though, if you have even one class with a finalizer that has a significant number of instances floating around, it causes the gc workload to more than double for those objects. Since garbage collection is historically the feature that causes the most performance problems, this can be an issue. Also, many developers don't understand that there's no guarantee an object will be formally collected by the gc. In most applications that exit, there are still objects hanging around that have not yet been gc'd, and in that case the JVM will typically just exit and close. If your finalize() method had any important work yet unperformed (updating a log file, closing a file handle, closing a socket connection), then it will be left open and it will then become dependent upon the operating system to clean up these unclosed resources. Most of the modern desktop OSes are smart enough to do this (with a significant performance penalty), but that's not the point--there are a lot of real time or embedded system OSes that don't. In other words, if your finalizer must run, then you're depending on particular OS features to be present and you're no longer writing platform-independent, 100% Sun-certified Java anymore.
So, what's the proper way to handle this? Do what Sun does...provide a cleanup method and make it the responsibility of the caller using the object to properly dispose of it when done. java.net.Socket, for instance, has a close() method that the caller is expected to call when done using the socket connection. Ugly? Maybe. Elegant? Perhaps not (but perhaps--a strong argument can be made that this is simply a behavior that ought to be requested by the caller just like any other behavior of that class). Necessary? Yup.
[ August 24, 2004: Message edited by: sever oon ] [ August 24, 2004: Message edited by: sever oon ]