JIT Optimization and Weak References

Viewed 621

I have the following piece of code:

private final List<WeakReference<T>> slaves;

public void updateOrdering() {
  // removes void weak references 
  // and ensures that weak references are not voided 
  // during subsequent sort 
  List<T> unwrapped = unwrap();
  assert unwrapped.size() == this.slaves.size();
  // **** could be reimplemented without using unwrap() ****
  Collections.sort(this.slaves, CMP_IDX_SLV);
  unwrapped = null;// without this, ....
}

Method unwrap() just creates a list of T's referenced by the weak references in slaves and as a side effect eliminates the weak references referencing null in slaves. Then comes the sort which relies on that each member of slaves references some T; otherwise the code yields a NullPointerException.

Since unwrapped holds a reference on each T in slaves, during sorting no GC eliminates a T. Finally, unwrapped = null eliminates the reference on unwrapped and so releases GC again. Seems to work quite well.

Now my question:

If I remove unwrapped = null; this results in NullPointerExceptions when running many tests under some load. I suspect that the JIT eliminates List<T> unwrapped = unwrap(); and so GC applies to the T's in slaves during sorting.

Do you have another explanation? If you agree with me, is this a bug in the JIT?

I personally think that unwrapped = null should not be necessary, because unwrapped is removed from the frame as soon as updateOrdering() returns. Is there a specification what may be optimized and what is not?

Or did I do the thing in the wrong way? I have the idea to modify comparator that it allows weak references on null. What do you think about that?

Thanks for suggestions.

Add on (1)

Now I want to add some missing pieces of information: First of all Java version: java version "1.7.0_45" OpenJDK Runtime Environment (IcedTea 2.4.3) (suse-8.28.3-x86_64) OpenJDK 64-Bit Server VM (build 24.45-b08, mixed mode)

Then someone wanted to see method unwrap

private synchronized List<T> unwrap() {
List<T> res = new ArrayList<T>();
T cand;
WeakReference<T> slvRef;
Iterator<WeakReference<T>> iter = this.slaves.iterator();
while (iter.hasNext()) {
    slvRef = iter.next();
    cand = slvRef.get();
    if (cand == null) {
    iter.remove();
    continue;
    }
    assert cand != null;
    res.add(cand);
} // while (iter.hasNext())

return res;
}

Note that while iterating, void references are removed. In fact i replaced this method by

private synchronized List<T> unwrap() {
List<T> res = new ArrayList<T>();
for (T cand : this) {
    assert cand != null;
    res.add(cand);
}

return res;
}

using my own iterator but functionally this should be the same.

Then someone wantet the stacktrace. Here is a piece of it.

 java.lang.NullPointerException: null
 at WeakSlaveCollection$IdxComparator.compare(WeakSlaveCollection.java:44)
 at WeakSlaveCollection$IdxComparator.compare(WeakSlaveCollection.java:40)
 at java.util.TimSort.countRunAndMakeAscending(TimSort.java:324)
 at java.util.TimSort.sort(TimSort.java:189)
 at java.util.TimSort.sort(TimSort.java:173)
 at java.util.Arrays.sort(Arrays.java:659)
 at java.util.Collections.sort(Collections.java:217)
 at WeakSlaveCollection.updateOrdering(WeakSlaveCollection.java:183)

it points into the comparator, the line with the return.

static class IdxComparator 
    implements Comparator<WeakReference<? extends XSlaveNumber>> {
    public    int compare(WeakReference<? extends XSlaveNumber> slv1, 
              WeakReference<? extends XSlaveNumber> slv2) {
        return slv2.get().index()-slv1.get().index();
    }
} // class IdxComparator 

and finally,

 private final static IdxComparator CMP_IDX_SLV = new IdxComparator();

is an important constant.

Add on (2)

Observed now that indeed NPE occurs even if 'unwrapped = null' is present in updateOrdering().

Weak references may be removed by java runtime if no strict reference holds after jit optimization. The source code seems not important at all.

I solved the problem the following way:

public void updateOrdering() {
Collections.sort(this.slaves, CMP_IDX_SLV);
}

without any decoration inserted to prevent slaves to be garbage collected and the comparator in CMP_IDX_SLV enabled to handle weak references to null:

    public    int compare(WeakReference<? extends XSlaveNumber> slv1, 
              WeakReference<? extends XSlaveNumber> slv2) {
    XSlaveNumber sSlv1 = slv1.get();
    XSlaveNumber sSlv2 = slv2.get();
    if (sSlv1 == null) {
    return sSlv2 == null ? 0 : -1;
    }
    if (sSlv2 == null) {
    return +1;
    }
    assert sSlv1 != null && sSlv2 != null;

    return sSlv2.index()-sSlv1.index();
    }

As a side effect, ordering the underlying list List> slaves; puts the void weak references at the end of the list, where it can be collected later.

3 Answers

As of Java 9, the correct way to prevent the JIT from discarding unwrapped is to use Reference.reachabilityFence:

public void updateOrdering() {
  List<T> unwrapped = unwrap();
  Collections.sort(this.slaves, CMP_IDX_SLV);
  Reference.reachabilityFence(unwrapped);
}

The presence of the reachabilityFence call causes unwrapped to be considered strongly reachable before the call, preventing collection of unwrapped or its elements until the sort completes. (The strange way in which reachabilityFence's effects seem to propagate backward in time is because it behaves primarily as a JIT directive.) Without reachabilityFence, unwrapped can be collected once the JIT can prove it will never again be accessed, even though the variable is still in scope.

Related