Should Java 9 Cleaner be preferred to finalization?
Asked Answered
A

4

24

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.

Aires answered 18/10, 2018 at 17:45 Comment(2)
Probably. Java 9 deprecated finalize(). The Javadoc does explicitly say The Cleaner and PhantomReference provide more flexible and efficient ways to release resources when an object becomes unreachable.Taoism
JFYI in java 9 in contrast with java 8 we have to use PhantomReference for post-mortem cleanup. So when if we found PhantomReference instance in a queue it means that memory for referenced object was free. In java 8 we had to execure clean method explicitly to free memoryTalkie
A
-2

Use neither.

Trying to recover from resource leaks using Cleaner presents nearly as many challenges as finalize the worst of which, as mentioned by Holger, is premature finalization (which is a problem not only with finalize but with every kind of soft/weak/phantom reference). Even if you do your best to implement finalization correctly (and, again, I mean any kind of system that uses a soft/weak/phantom reference), you can never guarantee that the resource leaks won't lead to resource exhaustion. The unavoidable fact is that the GC doesn't know about your resources.

Instead, you should assume that resources will be closed correctly (via AutoCloseable, try-with-resources, reference counting, etc.), find and fix bugs rather than hope to work around them, and use finalization (in any of its forms) only as a debugging aid, much like assert.

Resource leaks must be fixed--not worked around.

Finalization should only be used as an assertion mechanism to (try to) notify you that a bug exists. To that end, I suggest taking a look at the Netty-derived almson-refcount. It offers an efficient resource leak detector based on weak references, and an optional reference-counting facility that is more flexible than the usual AutoCloseable. What makes its leak detector great is that it offers different levels of tracking (with different amounts of overhead) and you can use it to capture stack traces of where your leaked objects are allocated and used.

Aires answered 21/10, 2018 at 0:57 Comment(8)
A leak is something different. You can have memory leaks, yet nobody claims the garbage collector should fix those. Memory is a resource like any other, which preferably is released ASAP. For the memory resource there is a standard mechanism. This mechanism should be available for other types of resources as well. There will be situations where you want to release such resources as quick as possible, but there are far more where "eventually" is just fine, as already seen in many applications that donot close sockets, files, etc, and still manage to work fine thanks to finalization.Epigrammatist
@Epigrammatist Did you read the part about premature finalization? Also, do you really not close your files? Your comment is so messed up, I am shocked you call yourself a "Java specialist."Aires
Strange, @AleksandrDubinsky that those many applications which don't explicitly close files or sockets but use finalize do work just fine.Associative
@Associative Who are you? How did you form this opinion? Is there a cult of really bad Java programmers somewhere who teach each other not to close files and sockets? Every single Java tutorial and book teaches to close files. I'm honestly curious about your microcosm.Aires
I clearly said that applications which use finalize to to close files and sockets seem to work just fine. This is not "not closing them". It is not closing them immediately. And for that matter I didn't say I condone it (it would obviously be unwise in a heavily loaded server). Just that such applications do seem to work fine, as per @john16384's comment. You were a rude, supercilious smartass towards him, yet failed to cite any evidence which refuted his claim. You still haven't. I'm pulling you up for it. Where is it?Associative
@Associative "Seem" to work fine? Great argument. I'll explain the problems with finalization that you can read anywhere else, if you leave me alone. OSes have limits on open files and sockets. However, finalization runs when the GC is out of memory, not when the process or OS is out of file or socket handles. It's a broken concept, unless a file-, socket-, and everything-aware GC is introduced. Will an application that only opens a single file work fine? Even that's not guaranteed, because of the premature finalization problem introduced in Java 8.Aires
Introduced and fixed. If a bug is introduced in intrinsic locks, do you propose use of "synchronized" is inherently dangerous and should never be used? "Seem to work fine" <=> "work fine". Desktop apps which read the occasional properties file and don't explicitly close them, objectively are fine. Finalization does not happen just when the application runs out of memory. Garbage collection is generational. Resources like sockets/file readers tend to have fleeting lifetimes and never leave Eden space. Eden space objects are collected very frequently, regardless of memory use.Associative
@Associative Do you mean that premature finalization has been fixed? Can you share a source? Various GCs work differently. ZGC isn't generational (yet) and doesn't preemptively run at all (without an opt-in flag). And of course, the contract of finalize doesn't promise anything. Keeping even a single config file open can cause various problems depending on platform, from preventing filesystem unmounting to preventing the file from being modified (windows).Aires
T
25

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?

Tyre answered 19/10, 2018 at 9:11 Comment(6)
Thank you for this excellent answer! And yet, with all due respect, I think we are basically in agreement that 1) Cleaner/PhantomReference are just an optimized version of finalize (and there exists a lemma that optimized does not automatically mean better.) 2) Deprecation of finalize was an act of angst and desperation against n00bs. It's not actually going anywhere and it's not any less correct than Cleaner/PhantomReference.Aires
And don't get me wrong, I did just discover the utility of an efficient, flexible alternative-finalization solution after studying Netty. Netty has a reference-counted manual memory management system on top of native memory, and it uses WeakReferences (strangely, not PhantomReferences) in an optional and sampling memory leak detection strategy. Using finalize would have been too slow. Nevertheless, that's a rare case, and it is good advice to avoid unnecessary optimization.Aires
Well, finalize() should never have existed. And it’s never too late to admit that something was a bad idea. Regarding, WeakReference vs. PhantomReference, I also used WeakReference when I truly needed such thing. As said, before Java 9, phantom references were not automatically cleared, requiring the referent to get collected in a second cycle, which made it less efficient. Besides that, the only difference is that the object must be finalized before the phantom reference gets enqueued, so when you don’t use finalizers, there is no difference anymore.Tyre
Sigh, or perhaps these are all bad ideas. Instead, what should exist is a built-in, optimized, special-purpose mechanism for debugging resource leaks akin to Netty's ResourceLeakDetector followed by an expectation that leaks don't exist. Then we wouldn't need finalizers at all. Or better yet, we could have built-in automatic reference counting. Now that would be helpful.Aires
The Cleaner seems to be a rather poor replacement for the safety net that finalize provided, requiring a thread per type of object that needs such a safety net. At the very least the standard implementation could have provided a shared Cleaner before Java apps of any size start having dozens of junk threads hanging around to clean instances of a single class.Epigrammatist
@Epigrammatist a single Cleaner can be used for different type of objects. Frameworks are free to define their shared Cleaner; that's not different to ExecutorService. But making the cleaning actions of entirely unrelated resources depend on each other (a long running cleaning action can delay other cleaning actions) brings you to the very problems of finalize(). But more than often, the error starts with thinking that there was a need for such a "safety net" for a particular application.Tyre
S
4

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

Serpentine answered 18/10, 2018 at 17:57 Comment(6)
I'm just going to copy&paste my comment to Elliot: Well, I don't disagree that finalization can have all of the listed problems, but I fail to see why Cleaner is "more flexible and efficient." Performance issues? Check. Deadlocks/hangs? Why not. Errors causing resource leaks? Of course. Cancelability? I don't see any Cleanable.cancel. Order guarantees? None at all. Timing guarantees? Forget about it. Seriously, wtf are they trying to sell us?Aires
Not very familiar with the tone, yet one of the reasons to deprecate it from the linked documents for one to read mindprod.com/jgloss/finalize.htmlSerpentine
@AleksandrDubinsky Cleanable.clean(): Unregisters the cleanable and invokes the cleaning action. That's what you should call, e.g. in the close() method of you resource class. Then, the GC is only needed if calling close() has been forgotten. But when closing correctly, you get timely cleanup and unregistration, regardless of when the next GC will happen.Tyre
@Tyre Yup, I noticed that reading the code and included it in my answer. clean() calls Reference.clear() which prevents the reference from enqueing on the ReferenceQueue and also removes references to it so it can be GC'd in the first pass. Finalizer could offer the same feature because it also inherits Reference, except for the fact that there is no way to access it.Aires
@AleksandrDubinsky the fact that finalize() is implemented with a special Reference object, is an implementation detail. But worse than not being able to opt out, is the lack of control over the registration. An object of a class with a nontrivial finalize() method is registered when the Object() constructor has been executed, before the resource has been allocated, at a time when the object is not even initialized. There’s the so-called finalizer attack to get hands on an uninitialized or invalid object, even when the constructor threw an exception.Tyre
Could anyone explain how to use PhantomReference and PhantomReference + Cleaner ?Talkie
A
-2

Use neither.

Trying to recover from resource leaks using Cleaner presents nearly as many challenges as finalize the worst of which, as mentioned by Holger, is premature finalization (which is a problem not only with finalize but with every kind of soft/weak/phantom reference). Even if you do your best to implement finalization correctly (and, again, I mean any kind of system that uses a soft/weak/phantom reference), you can never guarantee that the resource leaks won't lead to resource exhaustion. The unavoidable fact is that the GC doesn't know about your resources.

Instead, you should assume that resources will be closed correctly (via AutoCloseable, try-with-resources, reference counting, etc.), find and fix bugs rather than hope to work around them, and use finalization (in any of its forms) only as a debugging aid, much like assert.

Resource leaks must be fixed--not worked around.

Finalization should only be used as an assertion mechanism to (try to) notify you that a bug exists. To that end, I suggest taking a look at the Netty-derived almson-refcount. It offers an efficient resource leak detector based on weak references, and an optional reference-counting facility that is more flexible than the usual AutoCloseable. What makes its leak detector great is that it offers different levels of tracking (with different amounts of overhead) and you can use it to capture stack traces of where your leaked objects are allocated and used.

Aires answered 21/10, 2018 at 0:57 Comment(8)
A leak is something different. You can have memory leaks, yet nobody claims the garbage collector should fix those. Memory is a resource like any other, which preferably is released ASAP. For the memory resource there is a standard mechanism. This mechanism should be available for other types of resources as well. There will be situations where you want to release such resources as quick as possible, but there are far more where "eventually" is just fine, as already seen in many applications that donot close sockets, files, etc, and still manage to work fine thanks to finalization.Epigrammatist
@Epigrammatist Did you read the part about premature finalization? Also, do you really not close your files? Your comment is so messed up, I am shocked you call yourself a "Java specialist."Aires
Strange, @AleksandrDubinsky that those many applications which don't explicitly close files or sockets but use finalize do work just fine.Associative
@Associative Who are you? How did you form this opinion? Is there a cult of really bad Java programmers somewhere who teach each other not to close files and sockets? Every single Java tutorial and book teaches to close files. I'm honestly curious about your microcosm.Aires
I clearly said that applications which use finalize to to close files and sockets seem to work just fine. This is not "not closing them". It is not closing them immediately. And for that matter I didn't say I condone it (it would obviously be unwise in a heavily loaded server). Just that such applications do seem to work fine, as per @john16384's comment. You were a rude, supercilious smartass towards him, yet failed to cite any evidence which refuted his claim. You still haven't. I'm pulling you up for it. Where is it?Associative
@Associative "Seem" to work fine? Great argument. I'll explain the problems with finalization that you can read anywhere else, if you leave me alone. OSes have limits on open files and sockets. However, finalization runs when the GC is out of memory, not when the process or OS is out of file or socket handles. It's a broken concept, unless a file-, socket-, and everything-aware GC is introduced. Will an application that only opens a single file work fine? Even that's not guaranteed, because of the premature finalization problem introduced in Java 8.Aires
Introduced and fixed. If a bug is introduced in intrinsic locks, do you propose use of "synchronized" is inherently dangerous and should never be used? "Seem to work fine" <=> "work fine". Desktop apps which read the occasional properties file and don't explicitly close them, objectively are fine. Finalization does not happen just when the application runs out of memory. Garbage collection is generational. Resources like sockets/file readers tend to have fleeting lifetimes and never leave Eden space. Eden space objects are collected very frequently, regardless of memory use.Associative
@Associative Do you mean that premature finalization has been fixed? Can you share a source? Various GCs work differently. ZGC isn't generational (yet) and doesn't preemptively run at all (without an opt-in flag). And of course, the contract of finalize doesn't promise anything. Keeping even a single config file open can cause various problems depending on platform, from preventing filesystem unmounting to preventing the file from being modified (windows).Aires
A
-5

Java 9's Cleaner is very similar to traditional finalization (as implemented in OpenJDK), and almost everything (good or bad) that can be said about finalization can be said about Cleaner. Both rely on the garbage collector to place Reference objects on a ReferenceQueue and use a separate thread to run the cleanup methods.

The three main differences are that Cleaner uses PhantomReference instead of what is essentially a WeakReference (phantom reference doesn't allow you to access the object, which ensures it cannot be made reachable, ie zombified), uses a separate thread per Cleaner with a customizable ThreadFactory, and allows the PhantomReferences to be cleared (ie, cancelled) manually and never enqueued.

This provides performance advantages when heavy use is made of Cleaner/finalization. (Unfortunately, I don't have benchmarks to say how much of an advantage.) However, making heavy use of finalization is not normal.

For the normal things that finalize is used for--ie, a last-resort clean-up mechanism for native resources implemented with small, final objects that hold the minimum necessary state, provide AutoCloseable, and aren't allocated millions per second--there is no practical difference between the two approaches other than usage differences (in some aspects finalize is simpler to implement, in others Cleaner helps avoid mistakes). Cleaner doesn't provide any additional guarantees or behaviors (such as guaranteeing that cleaners will run prior to the process exiting--which is essentially impossible to guarantee anyway).

However, finalize has been deprecated. So that's that, I guess. Kind of a dick move. Perhaps the JDK developers are thinking, "why should the JDK provide a native mechanism that can easily be implemented as a library" "n00bs. n00bs everywhere. n00bs, stop using finalize, we hate you so much." It is a good point--and yet, I can't imagine finalize actually disappearing.

A good article that talks about finalization and outlines how alternative-finalization works can be found here: How to Handle Java Finalization's Memory-Retention Issues It paints in broad strokes how Cleaner works.

An example of the kind of code that might use Cleaner or PhantomReference instead of finalize is Netty's reference-counted manual management of direct (non-heap) memory. There, a lot of finalizeable objects get allocated and the alternative-finalization mechanism adopted by Netty makes sense. However, Netty goes a step farther and doesn't create a Reference for each reference-counted object unless the leak detector is set to its highest sensitivity. During usual operation it either doesn't use finalization at all (because if there is a resource leak, you're going to find out about it eventually anyway) or uses sampling (attaches clean-up code to a small fraction of allocated objects).

Netty's ResourceLeakDetector is much cooler than Cleaner.

Aires answered 18/10, 2018 at 19:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.