RFR 9: 8138696 : java.lang.ref.Cleaner - an easy to use alternative to finalization

Roger Riggs Roger.Riggs at Oracle.com
Thu Oct 15 16:00:57 UTC 2015


Hi Peter,

It was not the (my/our) intention for the Cleaner to be able to 
completely replace finalization.
The emphasis is on keeping it simple and efficient and covering the 99% 
of the cases.


On 10/15/2015 9:49 AM, Peter Levart wrote:
>
>
> On 10/15/2015 03:21 PM, Roger Riggs wrote:
>> Peter, Chris,
>>
>> Plausible but getting complicated as Chris observed.
>>
>> We can be on the lookout for specific cases in the JDK like that. I 
>> expect most
>> can be resolved by specific cooperation between the super/subclasses.
>
> How? Such "cooperation" can not use the tracked object's instance 
> methods as the instance is gone when cleanup happens, so no overriding 
> is possible. Can you rewrite this example:
>
> class SuperClass {
>     protected final Cleanup cleanup = 
> XXX.getCleaner().phantomCleanup(this);
>
>     SuperClass() {
>         cleanup.append(() -> {... super class cleanup ...});
>     }
> }
>
> class SubClass extends SuperClass {
>     SubClass() {
>         super();
>         cleanup.prepend(() -> {... pre-actions ...})
>                     .append(() -> {... post-actions ...});
>     }
> }
>
>
> ...using just current simple Cleaner API?
>
> I think that if we design an API to replace finalize(), then it should 
> support use cases that are possible with finalize(). Not just use 
> cases in the JDK itself, but also elsewhere on the planet.
I'd be interested in the other cases, but it is not a goal to solve 
every finalization problem.
>
>> The cleanup of each class should *only* be doing cleanup for their 
>> own class.
>> Because it is invoked after finalization, there can be *no* access to 
>> the instance
>> being cleaned.  Any shared state will be duplicated between the two 
>> cleanable behaviors.
>> If the subclass shares state with the superclass (protected fields or 
>> references)
>> then the cleanup needs to be co-designed.
>
> Even if state needed for clean-up is duplicated (it can just be doubly 
> referenced, for example: superclass establishes state and exposes it 
> to subclass that just references it), registering separate Cleanup 
> instances per super/subclass means that you can not control the order 
> of them fire-ing. If for example, the subclass cleanup logic needs the 
> superclass still be fully functional, it can do that now by:
>
> @Override protected void finalize() {
>         ... subclass cleanup ... // flushing something - needs 
> superclass functionality
>         super.finalize();
> }
>
> With separate Cleanable(s) for super/subclass, the order is not 
> guaranteed.
The superclass can expose its Cleanable and additional functionality to 
prepend or append.
That function do not exclusively need to be supported by the 
Cleaner/Cleanable types.

The order can be ensured by registering the cleanups with each other and 
delegating
to the other to achieve a desired execution order.

This would put the necessary mechanisms in the types that need 
specialized behavior
without adding complexity to the Cleaner.

If it turns out that it is a frequent pattern then it would make sense 
to look at it again.

Regards, Roger


>
>>
>> Canceling cleanup is easier, because the object has not been finalized.
>> If such a function is needed, it should be part of the API of the 
>> superclass
>> and part of the contract.
>
> Ideally such methods would be in superclass only and be final and just 
> delegate to Cleanable.clear() or Cleanable.clean(). The Cleanable 
> should be the only place where the cleanup logic is encapsulated.
>
>>
>> Also, I've seen a few calls to super.finalize() where there were no 
>> finalizers
>> in any of the superclasses.  It would be considered good design to 
>> always include it.
>> I don't know if the optimization for empty finalize methods includes the
>> case where it only calls super.finalize().
>
> Ah, I forgot that empty finalize() is optimized away (the Object is 
> not even registered for finalization).
>
> Regards, Peter
>
>>
>> Roger
>>
>> On 10/15/15 7:43 AM, Chris Hegarty wrote:
>>> Peter,
>>>
>>> On 15 Oct 2015, at 09:12, Peter Levart <peter.levart at gmail.com> wrote:
>>>
>>>> On 10/14/2015 07:43 PM, Roger Riggs wrote:
>>>>> Hi Alan, Mandy,
>>>>>
>>>>> I looked at a few of the many uses of finalize and the likely 
>>>>> changes.
>>>>> The zip Inflater and Deflater are relatively simple cases.
>>>>> Some finalizers are not used and can be removed.
>>>>> The sun.net.www.MeteredStream example subclasses PhantomCleanable 
>>>>> to add the state and cleanup
>>>>> behavior.
>>>>>
>>>>> http://cr.openjdk.java.net/~rriggs/webrev-cleaning-finalizers/
>>>>>
>>>>> Some of the harder cases will take more time to disentangle the 
>>>>> cleanup code.
>>>>> For example, ZipFile, and FileIn/OutputStream (Peter has 
>>>>> prototyped this).
>>>>>
>>>>> Roger
>>>> Hi Roger,
>>>>
>>>> It's good to see some actual uses of the API and how it is supposed 
>>>> to be used in migration from finalize() methods. I think empty 
>>>> protected finalize() method is almost always safe to remove. If the 
>>>> class is not subclassed, it serves no purpose (unless some other 
>>>> same-package class or itself is calling it, which can be checked 
>>>> and those calls removed). If subclass overrides finalize() and 
>>>> calls super.finalize(), its ok (it will call Object.finalize then 
>>>> when empty finalize() is removed). The same holds if a subclass 
>>>> calls finalize() as a virtual method regardless of whether it also 
>>>> overrides it or not.
>>>>
>>>> One thing to watch for is in case a subclass overrides finalize() 
>>>> like this:
>>>>
>>>> class Subclass extends Superclass {
>>>> ...
>>>> @Override protected finalize() {
>>>>     .... pre-actions ...
>>>>     super.finalize();
>>>>     ... post-actions...
>>>> }
>>>>
>>>> ... where the order of cleanup actions has to be orchestrated 
>>>> between super and subclass. Having a PhantomCleanable replace the 
>>>> finalize() in a superclass has a similar effect as the following 
>>>> re-ordering in subclass:
>>>>
>>>> @Override protected finalize() {
>>>>     .... pre-actions ...
>>>>     ... post-actions...
>>>>     super.finalize();
>>>> }
>>>>
>>>> ...since finalization is performed before PhantomReference is 
>>>> enqueued. This re-ordering is luckily often safe as post-actions 
>>>> usually can't use superclass resources any more and usually don't 
>>>> depend on the state of superclass. In addition, when superclass 
>>>> actions do happen, they can't invoke any instance methods if they 
>>>> are refactored to use Cleaner.
>>>>
>>>> This brings up an interesting question. finalize() method allows 
>>>> subclasses to override it and augment cleanup logic to include any 
>>>> state changes or resources used by subclass.
>>> Or for a subclass to effectively cancel any clean up, by
>>> providing an empty finalize() method. Which I think is
>>> also supported by your proposal, or at least a side-effect
>>> of having the Cleanup as a protected field ( you can call
>>> clear on it, right? ).
>>>
>>> Having the Cleanup as a protected field looks a little odd,
>>> but no more so than the public/protected finalize method.
>>>
>>> This is now getting even more complicated. There are
>>> potentially multiple object references being tracked as
>>> part of the cleanup of a single “significant” object ?
>>>
>>> -Chris.
>>>
>>>> How about Cleanup API? Subclass can register it's own Cleanable for 
>>>> own resources, but order of execution of superclass and subclass 
>>>> Cleanable(s) is arbitrary then. Cleanables will often be 
>>>> established in constructors and super/subclass constructors have a 
>>>> defined order of execution. So what about the following:
>>>>
>>>> public class Cleaner {
>>>>
>>>> public Cleanup phantomCleanup(Object referent);
>>>>
>>>> public interface Cleanable {
>>>>     void clean();
>>>>     void clear();
>>>> }
>>>>
>>>> public interface Cleanup extends Cleanable {
>>>>     Cleanable append(Runnable action);
>>>>     Cleanable prepend(Runnable action);
>>>> }
>>>>
>>>> public static abstract class PhantomCleanable extends 
>>>> PhantomReference implements Cleanable { ... }
>>>>
>>>> private static final class PhantomCleanup extends PhantomCleanable 
>>>> implements Cleanup { ... }
>>>>
>>>> ...use...
>>>>
>>>> class SuperClass {
>>>>     protected final Cleanup cleanup = 
>>>> XXX.getCleaner().phantomCleanup(this);
>>>>
>>>>     SuperClass() {
>>>>         cleanup.append(() -> {... super class cleanup ...});
>>>>     }
>>>> }
>>>>
>>>> class SubClass extends SuperClass {
>>>>     SubClass() {
>>>>         super();
>>>>         cleanup.prepend(() -> {... pre-actions ...})
>>>>                     .append(() -> {... post-actions ...});
>>>>     }
>>>> }
>>>>
>>>>
>>>> Regards, Peter
>>>>
>>>>>
>>>>>
>>>>> On 10/14/2015 10:23 AM, Alan Bateman wrote:
>>>>>> On 14/10/2015 15:03, Roger Riggs wrote:
>>>>>>> Hi Alan,
>>>>>>>
>>>>>>> So any user of the Cleaner can take advantage of the mechanism, 
>>>>>>> for example in a different package or module.
>>>>>>> For example, Netbeans.
>>>>>> Cleaner + Cleanable need to be public of course so maybe we 
>>>>>> should wait for the examples that extend WeakCleanableRef or cast 
>>>>>> the Cleanable to a WeakCleanableRef before seeing if this is the 
>>>>>> right thing or not.
>>>>>>
>>>>>> -Alan
>>
>




More information about the core-libs-dev mailing list