I have a shared resource for which I would like to know how many other objects are still using this resource. To do this I would like to use PhantomReference
s.
Since ReferenceQueue
s do not keep track of the references registered for them (source, section "Notification"), my idea was to store the reference as field of the tracked object:
class Foo {
private PhantomReference<Foo> thisReference;
public Foo(ReferenceQueue<Foo> queue) {
thisReference = new PhantomReference<>(this, queue);
}
}
Is this safe, based on Java 9(+) behavior of PhantomReference
s, or is it possible that an instance is garbage collected without the reference being added to the queue?
The documentation says:
Suppose the garbage collector determines at a certain point in time that an object is phantom reachable. At that time it will atomically clear all phantom references to that object and all phantom references to any other phantom-reachable objects from which that object is reachable. At the same time or at some later time it will enqueue those newly-cleared phantom references that are registered with reference queues.
But it does not mention whether the garbage collection can happen before the references are enqueued.
The package documentation contains the misleading phrase: “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.”
That “some phantom reference” does not emphasize the fact that the reference object itself must be reachable, which is stated in the Notification section:
The relationship between a registered reference object and its queue is one-sided. That is, a queue does not keep track of the references that are registered with it. If a registered reference becomes unreachable itself, then it will never be enqueued.
Since being phantom reachable has no practical consequences, other than potentially causing the enqueuing of phantom references, this is all that matters.
Note that the definitions of soft and weak reachability are better:
- An object is softly reachable if it is not strongly reachable but can be reached by traversing a soft reference.
- An object is weakly reachable if it is neither strongly nor softly reachable but can be reached by traversing a weak reference. …
Here, it is emphasized that the object must be reachable through the reference objects, which implies that the reference objects must be reachable too.
The problem with defining phantom reachability in a similar way, is, that phantom reachable objects are actually unreachable, as the PhantomReference
overrides the get()
method to always return null
, so an application can not reach the referent and hence, not traverse any phantom reachable objects.
Perhaps, a better definition would have been
- An object is phantom reachable if it is neither strongly, softly, nor weakly reachable, it has been finalized, but can be reached by the garbage collector by traversing a phantom reference.
With that definition, it would be pretty clear that your example with a self-reference would not work, just like it wouldn’t work with a WeakReference
or SoftReference
. Note that when your class has no dedicated finalize()
method, there is no practical difference between using a WeakReference
or a PhantomReference
.
It seems that even the API designers did not understand the implications completely, as the specification prior to Java 9 contained the rule:
Unlike soft and weak references, phantom references are not automatically cleared by the garbage collector as they are enqueued. An object that is reachable via phantom references will remain so until all such references are cleared or themselves become unreachable.
which blatantly ignores the point that a phantom reachable object isn’t reachable, at least from an application point of view and keeping it phantom reachable from a garbage collector’s view has no practical benefit. Not making the object live again, is the very difference to finalization. But note that at this place, the documentation acknowledged that if a phantom reference itself becomes unreachable, the referent stops being phantom reachable.
Starting with Java 9, phantom references are automatically cleared when enqueued, so the only practical implication of an object’s transition from phantom reachable to unreachable, is the phantom reference being enqueued. Which requires the reference object to be reachable.