The first segment of Episode 23 of the Java Off-Heap podcast covered the deprecation of
Object.finalize in Java 9 and deprecation and finalization in general. Deprecation is a subject near and dear to my heart. The hosts even mentioned me by name. Thanks for the shout-out, guys!
I wanted to clarify a few points and to answer some of the questions that weren’t resolved in that segment of the show.
Java Finalizers vs. C++ Destructors
The role of Java’s finalizers differs from C++ destructors. In C++ (prior to the introduction of mechanisms like
shared_ptr) anytime you created something with
new in a constructor, you were required to call
delete on it in the destructor. People mistakenly carried this thinking over to Java, and they thought that it was necessary to write
finalize methods to null out references to other objects. (This was never necessary, and the fortunately the practice seems to have died out long ago.) In Java, the garbage collector cleans up anything that resides on the heap, so it’s rarely necessary to write a finalizer.
Finalizers are useful if an object creates resources that aren’t managed by the garbage collector. Examples of this are things like file descriptors or natively allocated (“off-heap”) memory. The garbage collector doesn’t clean these up, so something else has to. In the early days of Java, finalization was the only mechanism available for cleaning up non-heap resources.
The point of finalization is that it allows one last chance at cleanup after an object becomes unreachable, but before it’s actually collected. One of the problems with finalization is that it allows “resurrection” of an object. When an object’s
finalize method is called, it has a reference to
this — the object about to be collected. It can hook the
this reference back into the object graph, preventing the object from being collected. As a result, the object can’t simply be collected after the
finalize method returns. Instead, the garbage collector must run again in order to determine whether the object is truly unreachable and can therefore be collected.
The reference package
java.lang.ref was introduced all the way back in JDK 1.2. This package includes several different reference types, including
PhantomReference. The salient feature of
PhantomReference is that it doesn’t allow the object to be “resurrected.” It does this by making the contained reference be inaccessible. A holder of a phantom reference gets notified that the referent has become unreachable (strictly speaking, phantom-reachable) but there’s no way to get the referent out and hook it back into the object graph. This makes the garbage collector’s job easier.
Another advantage of a
PhantomReference is that, like the other reference types, it can be cleared explicitly. Suppose there’s an object that holds some external resource like a file descriptor. Typically, such objects have a
close method the application should call in order to release the descriptor. Prior to the introduction of the reference types, such objects also need a
finalize method in order to clean up if the application had failed to call
close. The problem is, even if the application has called
close, the collector needs to do finalization processing and then run again, as described above, in order to collect the object.
PhantomReference and the other reference types have a
clear method that explicitly clears the contained reference. An object that has released its native resources via an explicit call to a
close method would call
PhantomReference.clear. This avoids a subsequent reference processing step, allowing the object to be collected immediately when it becomes unreachable.
Why Deprecate Object.finalize Now?
A couple of things have changed. First, JEP 277 has clarified the meaning of deprecation in Java 9 so that it doesn’t imply that the API will be removed unless
forRemoval=true is specified. The deprecation of
Object.finalize is an “ordinary” deprecation in that it’s not being deprecated for removal. (At least, not yet.)
A second thing that’s changed in Java 9 is the introduction of a class
java.lang.ref.Cleaner. Reference processing is often fairly subtle, and there’s a lot of work to be done to create a reference queue and a thread to process references from that queue.
Cleaner is basically a wrapper around
PhantomReference that make reference handling easier.
What hasn’t changed is that for years, it’s been part of Java lore that using finalization is discouraged. It’s time to make a formal declaration, and the way to do this is to deprecate it.
Has Anything Ever Been Removed from Java SE?
The podcast episode mentioned a Quora answer by Cameron Purdy written in 2014, where he said that nothing had ever been removed from Java. When he wrote it, the statement was correct. Various features of the JDK had been removed (such as apt, the annotation processing tool), but public APIs had never been removed.
However, the following six APIs were deprecated in Java SE 8, and they have been removed from Java SE 9:
In addition, in Java SE 9, about 20 methods and six modules have been deprecated with
forRemoval=true, indicating our intent to remove them from the next major Java SE release. Some of the classes and methods to be removed include:
The modules deprecated for removal are the following:
So yes, we are getting serious about removing stuff!
Will Finalization Be Removed?
As mentioned earlier,
Object.finalize is not being deprecated for removal at this time. As such, its deprecation is merely a recommendation that developers consider migrating to alternative cleanup mechanisms. The recommended replacements are
PhantomReference and the new
That said, we do eventually want to get rid of finalization. It adds extra complexity to the garbage collector, and there are recurring cases where it causes performance problems.
Before we can get rid of it, though, we need to remove uses of it from the JDK. That’s more than just removing the overrides of
finalize and rewriting the code to use
Cleaner instead. The problem is that there are some public API classes in the JDK that override
finalize and specify its behavior. In turn, their subclasses might override
finalize and rely on the existing behavior of
super.finalize(). Removing the
finalize method would expose these subclasses to a potentially incompatible behavior change. This will need to be investigated carefully.
There might also be a transition period where calling of the
finalize method is controlled by a command-line option. This would allow testing of applications to see if they can cope without finalization. Only after a transition period would we consider removing the finalization mechanism entirely. We might even leave the
finalize method declaration around for binary compatibility purposes, even after the mechanism for calling it has been removed.
As you can see, removing finalization would require a long transition period spanning several JDK releases, taking several years. That’s all the more reason to start with deprecation now.