JDK-8149610 : (ref) Reference construction vs referent lifetime races
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang
  • Affected Version: 6,7,8
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: generic
  • CPU: generic
  • Submitted: 2016-02-11
  • Updated: 2024-09-10
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
Description
The java.lang.ref.Reference class constructor presently sets the referent, then the queue.  However, if a GC occurs between those assignments and the GC determines the referent is dead, the GC will add the partially constructed reference to the pending list.  If the reference processing thread then runs before the constructor assigns the queue field, it will encounter a reference with a null (not ReferenceQueue.NULL) queue and get an unhandled NPE.  And having the reference processing thread be more circumspect in its handling of the queue isn't sufficient, since then the notification will be lost.

A similar problem exists if a GC discovers the referent of a soft reference is not strongly reachable before the reference's timestamp field is set.  In this case the consequences are probably less severe than an exception; the reference will probably just be deemed a candidate for clearing because it appears to not have been accessed for a long time.

It seems like this sort of thing could arise with any reference class, where the reference might be enqueued and become accessible to a thread monitoring the queue before the reference is fully constructed.

A similar issue came up with the recently added java.lang.ref.Cleaner, where the referent needs to be kept alive until the reference has been recorded.  So it isn't even just "fully constructed" that can be a problem.  In that case the proposed solution was to use the new Reference.reachabilityFence to ensure the needed minimum lifetime of the referent.

I don't know of a general automatic solution to this issue; perhaps documentation describing the issue and referring to reachabilityFence?

Comments
I went through the Reference construction code recently, in response to this PR: https://github.com/openjdk/jdk/pull/20898 I thought of the same thing that Brent suggested on 2024-03-08, to put try/finally/RF somewhere in the construction of one of the Reference classes. It would be difficult to put it one of the Reference subclasses, because the try-statement would need to enclose the super() call, which is illegal. (Not sure if https://openjdk.org/jeps/8338287 Flexible Constructor Bodies helps this.) It might be useful to add the try/finally/RF inside the two-arg Reference constructor, around the assignment of the referent and queue fields. This should prevent GC from determining the Reference to be not-strongly reachable between these statements. Also, since we believe RF now provides the right memory visibility properties with respect to the GC threads, any subsequent GC will observe both fields to be initialized properly. (Actually that might be the reason for the queue field to be declared volatile.) This at least preserves the invariants of the Reference class and immediate subclasses themselves, to ensure those internals are properly initialized before the Reference is enqueued. So, adding try/finally/RF into the Reference constructor could be a partial fix. The more general problem that Kim pointed out still remains. That is, some user subclass of (say) WeakReference could get into a data race between the construction of the subclass and the clearing/enqueuing/dequeuing/cleanup. I don't have any ideas about that issue at the moment.
10-09-2024

Using reachabilityFence in the [Soft|&etc]Reference constructors doesn't help with the general problem of a partially constructed Reference being enqueued. The reachabilityFence would need to be in the most-derived class's constructor, or after the call to the constructor.
08-03-2024

Can/should the [Soft|Weak|Phantom]Reference constructors keep 'referent' reachable using reachabilityFence() ?
08-03-2024