Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should Java 9 Cleaner be preferred to finalization?

In Java, overriding the finalize method gets a bad rap, although I don't understand why. Classes like FileInputStream use it to ensure close gets called, in both Java 8 and Java 10. Nevertheless, Java 9 introduced java.lang.ref.Cleaner which uses the PhantomReference mechanism instead of GC finalization. At first, I thought it was just a way add finalization to third-party classes. However, the example given in its javadoc shows a use-case that can easily be rewritten with a finalizer.

Should I be rewriting all of my finalize methods in terms of Cleaner? (I don't have many, of course. Just some classes that use OS resources particularly for CUDA interop.)

As I can tell, Cleaner (via PhantomReference) avoids some of the dangers of finalizer. In particular, you don't have any access to the cleaned object and so you can't resurrect it or any of its fields.

However, that is the only advantage I can see. Cleaner is also non-trivial. In fact, it and finalization both use a ReferenceQueue! (Don't you just love how easy it is to read the JDK?) Is it faster than finalization? Does it avoid waiting for two GCs? Will it avoid heap exhaustion if many objects are queued for cleanup? (The answer to all of those would appear to me to be no.)

Finally, there's actually nothing guaranteeing to stop you from referencing the target object in the cleaning action. Be careful to read the long API Note! If you do end up referencing the object, the whole mechanism will silently break, unlike finalization which always tries to limp along. Finally, while the finalization thread is managed by the JVM, creating and holding Cleaner threads is your own responsibility.

like image 935
Aleksandr Dubinsky Avatar asked Oct 18 '18 17:10

Aleksandr Dubinsky


People also ask

Why Finalize method is deprecated in Java 9?

Why is the finalize() method deprecated in Java 9? Yes it could be used in wrong way (like save an object from garbage collecting [only one time though] or try to close some native resources within it [it's better than don't close at all though]) as well as many other methods could be used wrongly.

Why finalize () method should be avoided in Java?

“This method is inherently unsafe. It may result in finalizers being called on live objects while other threads are concurrently manipulating those objects, resulting in erratic behavior or deadlock.” So, in one way we can not guarantee the execution and in another way we the system in danger.

Is finalize deprecated in Java?

In Java 9, the finalize() method has been deprecated. The finalization mechanism is inherently problematic and can lead to performance issues, deadlocks, and hangs.

Should we override Finalize method?

Overriding the Finalize method. You should override Finalize for a class that uses unmanaged resources, such as file handles or database connections that must be released when the managed object that uses them is discarded during garbage collection.


2 Answers

You are not supposed to replace all finalize() methods with a Cleaner. The fact that the deprecation of finalize() method and the introduction of (a public) Cleaner happened in the same Java version, only indicates that a general work on the topic happened, not that one is supposed to be a substitute of the other.

Other related work of that Java version is the removal of the rule that a PhantomReference is not automatically cleared (yes, before Java 9, using a PhantomReference instead of finalize() still required two GC cycles to reclaim the object) and the introduction of Reference.reachabilityFence(…).

The first alternative to finalize(), is not to have a garbage collection dependent operation at all. It’s good when you say that you don’t have many, but I’ve seen entirely obsolete finalize() methods in the wild. The problem is that finalize() looks like an ordinary protected method and the tenacious myth that finalize() was some kind of destructor still is spread on some internet pages. Marking it deprecated allows to signal to the developer that this is not the case, without breaking compatibility. Using a mechanism requiring explicit registration helps understanding that this is not the normal program flow. And it doesn’t hurt when it looks more complicated than overriding a single method.

In case your class does encapsulate a non-heap resource, the documentation states:

Classes whose instances hold non-heap resources should provide a method to enable explicit release of those resources, and they should also implement AutoCloseable if appropriate.

(so that’s the preferred solution)

The Cleaner and PhantomReference provide more flexible and efficient ways to release resources when an object becomes unreachable.

So when you truly need interaction with the garbage collector, even this brief documentation comment names two alternatives, as PhantomReference is not mentioned as the hidden-from-developer backend of Cleaner here; using PhantomReference directly is an alternative to Cleaner, which might be even more complicated to use, but also provides even more control over timing and threads, including the possibility to cleanup within the same thread which used the resource. (Compare to WeakHashMap, which has such cleanup avoiding the expenses of thread safe constructs). It also allows dealing with exceptions thrown during the cleanup, in a better way than silently swallowing them.

But even Cleaner solves more problems that you are aware of.

A significant problem, is the time of registration.

  • An object of a class with a nontrivial finalize() method is registered when the Object() constructor has been executed. At this point, the object has not been initialized yet. If your initialization is terminated with an exception, the finalize() method still will be called. It might be tempting to solve this by the object’s data, e.g. setting an initialized flag to true, but you can only say this for your own instance data, but not for data of a subclass, which still has not been initialized when your constructor returns.

    Registering a cleaner requires a fully constructed Runnable holding all necessary data for the cleanup, without a reference to the object under construction. You may even defer the registration when the resource allocation did not happen in the constructor (think of an unbound Socket instance or a Frame which is not atomically connected to a display)

  • A finalize() method can be overridden, without calling the superclass method or failing to do this in the exceptional case. Preventing the method from overriding, by declaring it final, does not allow the subclasses to have such cleanup actions at all. In contrast, every class may register cleaners without interference to the other cleaners.

Granted, you could have solved such issues with encapsulated objects, however, the design of having a finalize() method for every class guided to the other, wrong direction.

  • As you already discovered, there is a clean() method, which allows to perform the cleanup action immediately and removing the cleaner. So when providing an explicit close method or even implementing AutoClosable, this is the preferred way of cleanup, timely disposing the resource and getting rid of all the problems of garbage collector based cleanup.

    Note that this harmonizes with the points mentioned above. There can be multiple cleaners for an object, e.g. registered by different classes in the hierarchy. Each of them can be triggered individually, with an intrinsic solution regarding access rights, only who registered the cleaner gets hands on the associated Cleanable to be able to invoke the clean() method.


That said, it is often overlooked that the worst thing that can happen when managing resources with the garbage collector, is not that the cleanup action may run later or never at all. The worst thing that can happen, is that it runs too early. See finalize() called on strongly reachable object in Java 8 for example. Or, a really nice one, JDK-8145304, Executors.newSingleThreadExecutor().submit(runnable) throws RejectedExecutionException, where a finalizer shuts down the executor service still in use.

Granted, just using Cleaner or PhantomReference does not solve this. But removing finalizers and implementing an alternative mechanism when truly needed, is an opportunity to carefully think about the topic and perhaps insert reachabilityFences where needed. The worst thing you can have, is a method that looks like being easy-to-use, when in fact, the topic is horribly complex and 99% of its use are potentially breaking some day.

Further, while the alternatives are more complex, you said yourself, they are rarely needed. This complexity should only affect a fraction of your code base. Any why should java.lang.Object, the base class for all classes, host a method addressing a rare corner case of Java programming?

like image 198
Holger Avatar answered Oct 19 '22 21:10

Holger


As pointed out by Elliott in comments, moving ahead with Java9+, the Object.finalize is deprecated and hence it makes more sense to implement methods using Cleaner. Also, from the release notes :

The java.lang.Object.finalize method has been deprecated. The finalization mechanism is inherently problematic and can lead to performance issues, deadlocks, and hangs. The java.lang.ref.Cleaner and java.lang.ref.PhantomReference provide more flexible and efficient ways to release resources when an object becomes unreachable.

Details in Bug Database - JDK-8165641

like image 35
Naman Avatar answered Oct 19 '22 22:10

Naman