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.
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.
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.lev...@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