It's understandable that there is resistance to your simply removing the finalize()
method. After all, it's doing some useful cleanup. The question is what to replace it with.
Making the item AutoCloseable
might help, though this only works if the item is used within a single lexical scope. This will enable you to use try-with-resources. If the item is not used within a lexical scope, for example, if it's plugged into several data structures, you can't really use try-with-resources. This is where you might need to perform some cleanup action after the item becomes unreachable.
The preferred replacement for finalization is to use something like a WeakReference
. Normal references are "strong" references. When all strong references are gone, leaving only weak references, the item becomes "weakly reachable". This enqueues the reference on a reference queue; essentially this is an event that says "the item is no longer strongly reachable" and the action to be taken upon receiving this event is to perform some cleanup action.
This kind of reference processing can be inconvenient, so there is another mechanism called Cleaner
that might be easier to use.
What made finalization convenient is that the finalize()
method is on the about-to-be-collected object itself. Among the problems with finalization is that this "resurrects" the object by creating a new strong reference to it.
The trick with reference or Cleaner processing is that you need to keep track of stuff that needs to be cleaned up without keeping a reference to the object it's associated with. In your case you have a DiskCacheItem
which is apparently held in a DiskCache
. If a DiskCacheItem
instance is about to be garbage collected, you want it removed from the DiskCache
. It's hard to tell from your code, but it seems like the cleanup action is to remove a File
object that might be associated with a DiskCacheItem
. If so, here's what to do:
Create a Cleaner
instance that will be responsible for performing cleanup actions.
Create a state class that contains the stuff to be cleaned up, in this case, a File
.
When a DiskCacheItem
is added to the DiskCache
, create an instance of the state class, and register it with the Cleaner, associating it with a cleanup action. In this case the cleanup action is to delete the file. This returns an instance of Cleanable
.
If there is an explicit "close" or "delete" action on DiskCacheItem
, have it call clean()
on the associated Cleanable object. This will invoke the cleanup action immediately.
Sometime in the future, if the DiskCacheItem
has not been deleted explicitly, and all strong references to it have been dropped, it will become phantom-reachable. This will cause the Cleaner to invoke the cleanup action.
This effectively replaces finalization and the finalize()
method, though you will have to rearrange your data structures somewhat to do this.
After all this, what's the point of replacing finalization with the Cleaner/Cleanable mechanism? First, you can explicitly clean up a Cleanable. When it eventually becomes unreachable, it's just garbage collected. No extra processing is done. With finalization, even if a finalizable object has already been logically cleaned up, the JVM still needs to run the finalize() method. Second, finalization requires an extra GC pass to ensure that the object hasn't been "resurrected". With reference processing and Cleaner, there is no possibility of "resurrecting" an object. Third, finalization is fragile, and it's difficult to implement a finalize() method correctly.