Here's another finalization-related issue, this time hopefully appropriate
for this list. This was inspired by looking at the Ugawa, Jones, and Ritson
paper from ISMM 2014, which I belatedly had a chance to look at.

The java.lang.ref spec says:

"An object is phantom reachable if it is neither strongly, softly, nor
weakly reachable, it has been finalized, and some phantom reference refers
to it."

It notably does not say that such an object must not be reachable from
unfinalized objects.

I currently believe that:

1) This spec is not as intended, in that it allows a PhantomReference to X
to be enqueued while X is still actively being used. My understanding is
that PhantomReferences were invented largely to make that impossible.

2) Real production implementations enforce a stronger requirement, which
includes that the PhantomReference must not reachable from unfinalized
objects with a nontrivial finalizer, which prevents this problem.

3) The ISMM 2014 paper may have been confused by this, in that it seems to
mirror the official spec rather than the usual implementation. It
(surprisingly to me) does not appear to address the fact that
implementations generally mark reachable objects in at least two stages:
(1) Reachability from roots, and (2) Reachability from roots U unfinalized
finalizable objects, where the result of the first phase is used to
determine WeakReference clearing, while the result of the second phase
determines PhantomReference clearing, and what to collect.

Am I correct?

A scenario that I believe can fail according to the spec, but cannot and
must not fail in real life, is the following, where F1 and F2 are objects
with nontrivial finalizers, and P is the referent of a PhantomReference:

Consider F1 --> P,  where P has a PhantomReference referring to it, and
<root> -> F2 -> null.  Then

1) F1's finalizer runs and notionally P's (empty) finalizer runs. F1
modifies F2, so it gets a strong reference to P.

[ P has now been finalized. We have <root> -> F2 -> P ]

2) <root> is cleared, making F2 unreachable.

[ P is not strongly, softly or weakly referenced, and has been finalized.
Therefore P is phantom reachable. ]

3) The PhantomReference to P is enqueued, resulting in running a Cleaner
that e.g. deallocates native memory required by P.

4) F2's finalizer runs and accesses P.

5) Bad stuff.

Although this is arguably a weird corner case that is unlikely to occur
frequently, I think it profoundly changes the algorithms used to implement
this. "Has been finalized" is not the correct check; it's reachability from
a not-yet-finalized object that matters. Hence the implementation must do a
reachability analysis not technically required by the current spec.

[ Just saying that in the spec probably doesn't work either. I suspect the
fact that the finalizer is nontrivial also matters to get reasonable
progress guarantees. Currently I think the spec doesn't have that notion,
but it seems annoyingly essential. ]

Clearly, this problem goes away if you get rid of finalizers and merge
{Phantom,Weak}References, which is presumably the intended end state, but
not one that looks imminent to me.

Hans

Reply via email to