Description
Affects: 5.2.2 and 5.0.12
ConcurrentReferenceHashMap
with reference type WEAK
have entries disappear on garbage collections even though the keys and values stored in the map have strong references outside of the map. Given enough memory pressure, SOFT
entries would probably fail similarly.
I suspect this happens because the ConcurrentReferenceHashMap.WeakEntryReference
class (which extends WeakReference<Entry>
) is the only thing (weakly) referencing any Entry. The Segment.references
items are all WeakReference
s. Think you need to refactor the map to directly have WeakReference<K>
and WeakReference<V>
instances instead of WeakReference<Entry<K,V>
.
Soft references appear to be handled the identically, so any fix should be applied to those references as well of course.
Sample failing unit test (fails with AdoptOpenJDK version 8u222, issue was spotted on IBM JDK 8):
package test;
import org.junit.Test;
import org.springframework.util.Assert;
import org.springframework.util.ConcurrentReferenceHashMap;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentMap;
public class WeakConcurrentReferenceHashMapTest {
@Test
public void testWeakConcurrentReferenceHashMap() {
final ConcurrentMap<Integer, Integer> map =
new ConcurrentReferenceHashMap<>(10, ConcurrentReferenceHashMap.ReferenceType.WEAK);
final List<Integer> doNotGc = new ArrayList<>();
final Integer key = new Integer(123456);
final Integer value = new Integer(888888);
doNotGc.add(key);
doNotGc.add(value);
final WeakReference<Integer> weakKey = new WeakReference<>(key);
final WeakReference<Integer> weakValue = new WeakReference<>(value);
// put it there
map.put(key, value);
Assert.isTrue(Objects.equals(value, map.get(key)), "1st get");
// wipe map (comment out to make asserts pass)
System.gc();
// A weak reference to key or value should still be reachable after the GC
Assert.notNull(weakKey.get(), "weak key ref");
Assert.notNull(weakValue.get(), "weak value ref");
// map.get() returns null
Assert.isTrue(Objects.equals(value, map.get(key)), "get post-GC");
Assert.isTrue(doNotGc.size() == 2, "I still remember all");
}
}