0

I have faced the problem sometimes my weak reference to some object just disappeared, even if I had the strong reference remembered in the local variable. But it seems to me this can have something to do with the JIT optimization. Today with Java 11 it seems to me I could find the repeatable confirmation of this behavior. So it is possible, that in the case the reference to some content of the weak reference is remembered in the local variable, it may not prevent this object from garbage collection. I could reproduce the problem with following construct:

SomeObjectStructure locId = generateIdAndRememberInternallyWeakReferenceToIdObject();
// This did not work:
// Reference.reachabilityFence(locId);
// The only working solution for my singleton was to assign 
// the Id to the ThreadLocal field value of the singleton:
// currentThreadId.set(locId)
try {
  // do some stuff here, but do not work with variable locId
  // directly, only try to ask for the weak reference sometimes
  somethingAskingWeakReferenceToId();
  // In the above method sometimes, the weak reference shows null, so
  // even if one would think, the local variable locId 
  // is preventing the object from garbage collection, it does not.
  // There were situations where at this time the id has
  // already gone. In other cases it was still there.
  // This has then serious consequences if you rely 
  // on the presence of the Id in the weak reference 
  // container in the code here:
  doSomethingOnTheBasisOfContentOfRememberedWeakReference();
} finally {
  // Following statement has been intended to release the strong reference 
  // locId to allow the GC of the the weakly internally remembered Id
  // after the finally block ends, but 
  // sometimes this can be ignored by the compiler 
  // or optimizer, the variable 
  // could be released much earlier than after this place in the code:
  locId = null;
  // Instead of the above statement I used later this one:
  // currentThreadId.set(null);
  // to release the locId remembered in the ThreadLocal variable 
  // of class containing the code.
}

I found the page to the Oracle optimizations and similar stuff could be possibly also concluded from it, see here. Am I right, is it so? Or is there some other explanation of this behavior? If I remembered the id additionally into some ThreadLocal variable (my objects are singletons), then the problem disappeared too. Also in this question the similar problem is discussed and the solution is proposed to use

Reference.reachabilityFence(locId);

right after the variable is assigned which I also tested, but in my case this did not help and the variable content still has been garbage collected before reaching the finally block... Why is it so?

But the solution with the fence would have also the drawback, the variable content, if I understood it correctly, should be then remembered not until the visibility scope of the variable ends, but until the method is finished. But it still did not work...

Is there any other way how to prevent this behavior of the compiler/optimizer?

  • Is the local variable ever used again? The rules about reachability are pretty specific. – Louis Wasserman May 01 '23 at 19:27
  • 2
    When you say "*my weak reference to some object just disappeared*", do you mean that you find the weak reference object to have been *cleared*, such that its `get()` method returns `null`? – John Bollinger May 01 '23 at 19:35

1 Answers1

3

I have faced the problem sometimes my weak reference to some object just disappeared, even if I had the strong reference remembered in the local variable.

The very nature of a weak reference is that it does not prevent the referenced object from being GC'd. I understand that it is surprising for that to happen within the scope of a local variable holding a strong reference to the same object, but this is in fact possible. Since you describe performing your test with Java 11, here is the relevant text from JLS 11:

A reachable object is any object that can be accessed in any potential continuing computation from any live thread.

A finalizer-reachable object can be reached from some finalizable object through some chain of references, but not from any live thread.

An unreachable object cannot be reached by either means.

That has to be read carefully. In particular, note that the definition for an object being reachable is not based (solely) on references being held, but rather on whether the object can actually be accessed, which is a stronger condition. (Access via Reference objects is explicitly excluded, albeit not by that text.) If there is no code path that any thread can execute that will access the object in question, then it is not reachable.

That text is crafted specifically to allow objects to be collected in situations such as you present, where one or more strong references are held, but, based on the actual code of the program, it can be determined that the object will not actually be accessed (unless via a Reference object).

Or is there some other explanation of this behavior?

There is no particular explanation required. What you describe is an allowed behavior. It may be more likely to be exercised by code that has been JITed, but that would be a feature, not a bug.

Also in this question the similar problem is discussed and the solution is proposed to use

Reference.reachabilityFence(locId);

right after the variable is assigned which I also tested, but in my case this did not help and the variable content still has been garbage collected before reaching the finally block... Why is it so?

You have misunderstood the use of Reference.reachabilityFence(). It does not prevent the specified object from becoming unreachable in the future, but rather from having become unreachable in the past. Using it right after the variable assignment is therefore pointless. On the other hand, using it in the finally block of your example should prevent the object from being collected (or the reference cleared) before execution of the try block terminates.

Is there any other way how to prevent this behavior of the compiler/optimizer?

See above. But also, if this is causing you trouble then you're probably misusing weak references. Another solution, then, would be to use strong references instead.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • 2
    It’s worth noting that even accessing the object does not necessarily prevent its collection, as the optimizer could transform the code into a form that works without the access. The spec: [“*Optimizing transformations of a program can be designed that reduce the number of objects that are reachable to be less than those which would naively be considered reachable*”](https://docs.oracle.com/javase/specs/jls/se11/html/jls-12.html#jls-12.6.1:~:text=Optimizing%20transformations,considered%20reachable.) See also [this known real life issue](https://stackoverflow.com/q/26642153/2711488) – Holger May 02 '23 at 06:44
  • Thank you very much, especially the clarification of the RechabilityFence, which I did not know before... – Artur Linhart May 03 '23 at 13:00
  • @Holger - yes, this is what I also concluded from the example from Oracle... Thank you for sharing the link to the clarification of the reachability. – Artur Linhart May 03 '23 at 13:02