Java SoftReference奇怪的行为

时间:2018-12-27 14:34:55

标签: java garbage-collection soft-references

Map<E, SoftReference<T>> cache = new ConcurrentHashMap<E, SoftReference<T>>();

我已将地图声明为与我用作缓存的上述地图相同的地图。

问题是,我要在将项目添加到缓存后立即对缓存执行所有操作,但是以后不能执行。

例如:

cache.add("Username", "Tom");

if(cache.contains("Username"))返回true,但是

String userName = (String)cache.get("Username")返回null。

这种情况仅在很长时间之后才会发生。

如果在将其添加到缓存几个小时后得到该值,则可以正确获取该值。 如果经过很长时间(例如15到20个小时以上)后才获得该值,那么我将得到null。

当GC清除SoftReference对象时,密钥是否仍保留在HashMap中?这是这种行为的原因吗?

3 个答案:

答案 0 :(得分:2)

SoftReference的引用对象被垃圾回收时,SoftReference被清除 ,即其引用字段设置为null

因此,键不仅保留在映射中,而且相关值SoftReference实例也保留在映射中,即使其引用字段为null

但是由于您声明的Map<E, SoftReference<T>>字段与cache.add("Username", "Tom")(String)cache.get("Username")的调用者之间必须存在一层(您没有显示),所以甚至可能出现这种情况层正确处理。

为完整起见,正确的实现可能看起来像

final Map<E, SoftReference<T>> cache = new ConcurrentHashMap<>();

/** remove all cleared references */
private void clean() {
    cache.values().removeIf(r -> r.get() == null);
}
public void add(E key, T value) {
    clean();
    cache.put(key, new SoftReference<>(value));
}
public boolean contains(E key) {
    clean();
    return cache.computeIfPresent(key, (e,r) -> r.get()==null? null: r) != null;
}
public T get(E key) {
    clean();
    for(;;) {
        SoftReference<T> ref = cache.computeIfPresent(key, (e,r) -> r.get()==null? null: r);
        if(ref == null) return null;
        T value = ref.get();
        if(value != null) return value;
    }
}

此代码可确保在查询时自动删除所收集值的映射。另外,尽管不是必需的,clean()操作会删除所有收集的地图条目,以减少地图的空间(与WeakHashMap在内部的工作方式相比)。

但是请注意,仍然没有保证,当cache.contains("Username")返回true时,随后的cache.get("Username")将返回非null值。此问题也称为 check-then-act 反模式,它可能会失败,因为在检查和后续操作之间可能会发生并发更新(即使您正在使用缓存, (因为垃圾回收可能异步发生),因此仅落后于一个测试。

在这方面,contains操作在大多数情况下是无用的。您必须调用get,以接收对值的强引用,如果非null,则必须继续使用该引用。

答案 1 :(得分:1)

按照oracle docs

  

在虚拟机引发OutOfMemoryError之前,确保已清除所有对软可访问对象的软引用。

是当GC清除SoftReference对象时,密钥保留在HashMap中。键和对应的值除了在地图中时没有其他关系。将map's value用作常规参考,除非map是GC,否则它们将始终在map中。

答案 2 :(得分:0)

是的,这是正常的行为。

SoftReference是垃圾收集的,导致Map中的值设置为 null

与将其他类型的地图(例如Map)的某个键的值设置为null相同。