具有弱哈希映射的重复数据删除器的正确实现是什么?

时间:2017-09-12 18:55:39

标签: java garbage-collection weak-references

我在Aleksey Shipilev的幻灯片“The String catechism”(https://shipilev.net/talks/joker-Oct2014-string-catechism.pdf,幻灯片49 ff)中找到了“重复数据删除器”的概念。许多Java程序员都知道从String.intern()实习的类似概念 但是,如果没有使用弱引用,则重复数据删除器是潜在的内存泄漏。 我想知道具有弱哈希映射的重复数据删除器的正确实现必须如何。我倾向于选择B,但我不确定。

选项A: 使用WeakHashMap就足够了。 “弱键”可确保在不再使用对象时将其删除。

示例实施:

public class SimpleWeakHashMapDeduplicator {
    private final WeakHashMap<Object, Object> weakHashMap = new WeakHashMap<>();

    public Object deduplicate(Object potentialDuplicate) {
        if(potentialDuplicate == null) {
            return null;
        } else {
            return weakHashMap.computeIfAbsent(potentialDuplicate, (key)->key);
        }
    }
}

选项B: 使用WeakHashMap是不够的。所有值都必须是WeakReferences,因为ComplicatedWeakHashMapDeduplicator的实例强引用弱哈希映射,该哈希映射强引用数组,其中一个条目强引用该值。只有密钥被地图弱引用。我哪里错了?

示例实施:

public class ComplicatedWeakHashMapDeduplicator {
    private final WeakHashMap<Object, WeakReference<Object>> weakHashMap = new WeakHashMap<>();

    public Object deduplicate(Object potentialDuplicate) {
        if(potentialDuplicate == null) {
            return null;
        } else {
            return weakHashMap.computeIfAbsent(potentialDuplicate, WeakReference::new).get();
        }
    }
}

您怎么看?

2 个答案:

答案 0 :(得分:2)

你在“选项B”的正确轨道上,但你还没到那里。这条线存在问题:

return weakHashMap.computeIfAbsent(potentialDuplicate, WeakReference::new).get();

让我们暂时假设弱地图包含先前缓存的值。您拨打computeIfAbsent并获取参考。没有什么能阻止垃圾收集器在您get()之前的简短窗口期间回收它的指示对象。如果发生这种情况,您最终会返回null

您的逻辑需要更强大一些。尝试这样的事情:

public final class WeakCache<T> {
    private final WeakHashMap<T, WeakReference<T>> _map = new WeakHashMap<>();

    public synchronized T cache(final T value) {
        if (value == null) {
            return null;
        }

        final WeakReference<T> oldReference = _map.get(value);

        if (oldReference != null) {
            final T oldValue = oldReference.get();

            if (oldValue != null) {
                return oldValue;
            }
        }

        _map.put(value, new WeakReference<>(value));

        return value;
    }
}

这可以防止缓存的值泄漏,但是值得问一下,在发布旧值时你想要多么渴望。如果你的价值往往是短暂的,但预计会一次又一次地弹出,你可能想要更长时间地坚持下去。在这种情况下,您可以考虑使用SoftReference作为值包装器。软引用表现相似,但它们倾向于坚持其指示物,直到面临记忆压力。 Oracle的“服务器”VM(x64的默认值)更愿意扩展堆而不是释放软引用,因此应用程序的内存使用量可能会更快达到极限,此时它将开始驱逐无法访问的值。这是一种权衡,也不是“一刀切”的解决方案。灵活的实现可能会将引用创建抽象为可插入策略,这使得在首次创建缓存时在弱引用和软引用之间进行选择变得微不足道。

答案 1 :(得分:1)

我忽略了WeakHashMap的javadoc中的以下注释:

  

实施说明:WeakHashMap中的值对象由。持有   普通的强引用。因此,应该注意确保这一点   值对象不直接强烈引用自己的键   或间接地,因为这将防止密钥被丢弃。   请注意,值对象可以通过以下方式间接引用其键   WeakHashMap本身;也就是说,值对象可能强烈引用一些   其他关键对象,其关联的值对象反过来强烈   指第一个值对象的键。如果是地图中的值   一种方法,不要依赖地图持有强有力的参考   处理这个问题就是将值本身包装在WeakReferences中   在插入之前,如:m.put(key,new WeakReference(value)),和   然后解开每一个。