简体   繁体   English

Java SoftReference奇怪的行为

[英]Java SoftReference strange behaviour

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

I have map declared a map like the above one which I'm using as a Cache. 我有地图声明了一个像我上面用作Cache的地图。

The problem is I'm to perform all operations on the Cache immediately after adding an item to the Cache but not later. 问题是我将项目添加到缓存后立即执行缓存上的所有操作,但不是稍后。

For ex: 例如:

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

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

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

This happens only after a long time. 这种情况只在很长一段时间后发生。

If I get the value after a few hours of adding it to the cache, I get the value correctly. 如果我在将它添加到缓存几个小时后得到该值,我会正确获得该值。 If I get the value after a long time, say more than 15-20 hrs, I get null. 如果我在很长一段时间后得到这个值,比如超过15-20小时,我就会得到null。

When GC clears SoftReference objects, will the key remain in HashMap? 当GC清除SoftReference对象时,密钥是否仍保留在HashMap中? Is that the reason for this behaviour? 这是出现这种行为的原因吗?

When the referent of a SoftReference gets garbage collected, the SoftReference gets cleared , ie its referent field is set to null . SoftReference被垃圾收集时, SoftReference清除 ,即它的引用字段被设置为null

So not only does the key stay in the map, the associated value, the SoftReference instance, also stays in the map, even if its referent field is null . 因此,不仅密钥保留在地图中,相关的值SoftReference实例也会保留在地图中,即使其引用字段为null

But since there must be a layer between your declared Map<E, SoftReference<T>> field and the caller of cache.add("Username", "Tom") and (String)cache.get("Username") , which you didn't show, it might even be the case that this layer handles it correctly. 但是因为在你声明的Map<E, SoftReference<T>>字段和cache.add("Username", "Tom")(String)cache.get("Username")的调用者之间必须有一个层,你没有展示,甚至可能是这个图层正确处理它的情况。

For completeness, a correct implementation could look like 为了完整起见,正确的实现可能看起来像

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;
    }
}

This code ensures that mappings of collected values get removed atomically when queried. 此代码确保在查询时以原子方式删除收集值的映射。 Additionally, though not required, the clean() operation removes all collected entries of the map, to reduce the map's space (comparable to how WeakHashMap works internally). 此外,虽然不是必需的,但clean()操作会删除所有收集的地图条目,以减少地图的空间(与WeakHashMap内部工作方式相当)。

But note that still, there is no guaranty that when cache.contains("Username") returns true , a subsequent cache.get("Username") would return a non- null value. 但请注意,仍然没有保证当cache.contains("Username")返回true ,后续的cache.get("Username")将返回非null值。 This problem is also known as the check-then-act anti-pattern, which may fail because between the check and the subsequent action, a concurrent update may happen (here even if you're using the cache by only one thread, as the garbage collection may happen asynchronously), outdating the result of the preceding test. 这个问题也称为check-then-act反模式,它可能会失败,因为在检查和后续操作之间,可能会发生并发更新(这里即使你只使用一个线程的缓存,垃圾收集可能是异步发生的),过时的测试结果已经过时了。

In this regard, the contains operation is useless for most scenarios. 在这方面,对于大多数情况, contains操作是无用的。 You have to invoke get , to receive a strong reference to a value, and proceed with that reference, if non- null . 您必须调用get ,以接收对值的强引用,并继续该引用(如果为非null

As per oracle docs 按照oracle文档

All soft references to softly-reachable objects are guaranteed to have been cleared before the virtual machine throws an OutOfMemoryError. 在虚拟机抛出OutOfMemoryError之前,保证已清除对软可访问对象的所有软引用。

Yes When GC clears SoftReference objects, the key remain in HashMap. 是当GC清除SoftReference对象时,该键保留在HashMap中。 The key and corresponding value have no relation other than when they are inside a map. 除了它们在地图内部之外,键和相应的值没有任何关系。 Make map's value a normal reference and they will always be in the map unless map is GC. map's value设为普通参考map's value ,除非地图为GC,否则它们将始终位于地图中。

Yes, that is normal behaviour. 是的,这是正常的行为。

The SoftReference is garbage collected, resulting in the value in the Map being set to null . SoftReference被垃圾收集,导致Map中的值被设置为null

It is the same as setting the value of a certain key to null for other types of maps (eg. Map) 对于其他类型的地图(例如,地图),将某个键的值设置为null是相同的

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM