spring-projects / spring-projects/spring-framework

Weak ConcurrentReferenceHashMap forgets strongly reachable objects after GC

Open
#24,253 5 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

in: core type: documentation
Dominant language
Java
Stars
60.2k
Forks
38.8k
Avg merge
5d 2h
Merged PRs (30d)
27

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 WeakReferences. 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");
    }
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with ConcurrentReferenceHashMap.WeakEntryReference and inspect how Segment.references stores entries; compare the weak and soft reference paths. Run the provided WeakConcurrentReferenceHashMapTest and verify that strongly reachable keys and values remain available after GC for both WEAK and SOFT reference types.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.