javaconcurrencyreferencevolatileconsistency

Memory semantics of java.lang.ref.Reference methods


I am developing some concurrent algorithms which deal with Reference objects. I am using java 17.

The thing is I don't know what's the memory semantics of operations like get, clear or refersTo. It isn't documented in the Javadoc.

Looking into the source code of OpenJdk, the referent has no modifier, such as volatile (while the next pointer for reference queues is volatile). Also, get implementation is trivial, but it is an intrinsic candidate. clear and refersTo are native. So I don't know what they really do.

When the GC clears a reference, I have to assume that all threads will see it cleared, or otherwise they would see a reference to an object (in process of being) garbage collected, but it's just an informal guess.

Is there any warranty about the memory semantics of all these operations?

If there isn't, is there a way to obtain the same warranries of a volatile access by invoking, for instance, a fence operation before and/or after calling one of these operations?


Solution

  • When you invoke clear() on a reference object, it will only clear this particular Reference object without any impact on the rest of your application and no special memory semantics. It’s exactly like you have seen in the code, an assignment of null to a field which has no volatile modifier.

    Mind the documentation of clear():

    This method is invoked only by Java code; when the garbage collector clears references it does so directly, without invoking this method.

    So this is not related to the event of the GC clearing a reference. Your assumption “that all threads will see it cleared” when the GC clears a reference is correct. The documentation of WeakReference states:

    Suppose that the garbage collector determines at a certain point in time that an object is weakly reachable. At that time it will atomically clear all weak references to that object and all weak references to any other weakly-reachable objects from which that object is reachable through a chain of strong and soft references.

    So at this point, not only all threads will agree that a weak reference has been cleared, they will also agree that all weak references to the same object have been cleared. A similar statement can be found at SoftReference and PhantomReference.

    The Java Language Specification, §12.6.2. Interaction with the Memory Model refers to points where such an atomic clear may happen as reachability decision points. It specifies interactions between these points and other program actions, in terms of “comes-before di” and “comes-after di” relationships, the most import ones being:

    If r is a read that sees a write w and r comes-before di, then w must come-before di.

    If x and y are synchronization actions on the same variable or monitor such that so(x, y) (§17.4.4) and y comes-before di, then x must come-before di.

    So, the GC action will be inserted into the synchronization order and even a racy read could not subvert it, but it’s important to keep in mind that the exact location of the reachability decision point is not known to the application. It’s obviously somewhere between the last point where get() returned a non-null reference or refersTo(null) returned false and the first point where get() returned null or refersTo(null) returned true.

    For practical applications, the fact that once the reference reports the object to be garbage collected you can be sure that it won’t reappear anywhere¹, is enough. Just keep the reference object private, to be sure that not someone invoked clear() on it.

    ¹ Letting things like “finalizer resurrection aside”