Java WeakHashMap和缓存:为什么它引用了键而不是值?

时间:2009-11-26 10:16:51

标签: java caching weak-references

Java WeakHashMap经常被认为对缓存很有用。虽然它的弱引用是根据地图的键而不是它的值来定义的,但这似乎很奇怪。我的意思是,这是我想要缓存的值,除了缓存之外没有其他任何人强烈引用它们,我想要收集垃圾,不是吗?

以哪种方式保存对密钥的弱引用?如果你执行ExpensiveObject o = weakHashMap.get("some_key"),那么我希望缓存保持为'o',直到调用者不再持有强引用,并且我根本不关心字符串对象“some_key”。

我错过了什么吗?

4 个答案:

答案 0 :(得分:107)

WeakHashMap 不是作为缓存有用,至少是大多数人对它的看法。正如你所说,它使用弱,而不是弱,因此它不是为大多数人想要使用它而设计的(事实上,我已经< em>看到人们错误地使用它。

WeakHashMap主要用于保存关于其生命周期无法控制的对象的元数据。例如,如果您有一堆对象通过您的类,并且您希望跟踪有关它们的额外数据,而不需要在它们超出范围时得到通知,并且不参考它们使它们保持活动状态。

一个简单的例子(以及我以前用过的一个例子)可能是这样的:

WeakHashMap<Thread, SomeMetaData>

您可以跟踪系统中的各种线程正在做什么;当线程死亡时,该条目将从您的地图中静默删除,如果您是最后一次引用该线程,则不会使该线程被垃圾收集。然后,您可以迭代该映射中的条目,以找出有关系统中活动线程的元数据。

有关详细信息,请参阅WeakHashMap in not a cache!

对于您所使用的缓存类型,请使用专用缓存系统(例如EHCache)或查看google-collections' MapMaker class;

之类的东西
new MapMaker().weakValues().makeMap();

会做你想做的事情,或者如果你想获得幻想,你可以添加定时过期时间:

new MapMaker().weakValues().expiration(5, TimeUnit.MINUTES).makeMap();

答案 1 :(得分:32)

WeakHashMap的主要用途是当你的键映射消失时你想要消失的映射。缓存是相反的 - 当你的值消失时,你想要消失的映射。

对于缓存,您想要的是Map<K,SoftReference<V>>。当内存变紧时,SoftReference将被垃圾收集。 (将其与WeakReference进行对比,只要不再对其指示对象进行硬引用,就可以将其清除。)您希望引用在缓存中是软的(至少在键值映射中是这样的)不要过时),因为如果你以后再查找它们,你的值有可能仍然在缓存中。如果引用很弱,那么你的值将立即被gc'd,从而破坏了缓存的目的。

为方便起见,您可能希望隐藏SoftReference实施中的Map值,以便您的缓存显示为<K,V>类型,而不是<K,SoftReference<V>>。如果您想这样做,this question会对网上提供的实施提出建议。

另请注意,当您在SoftReference中使用Map值时,必须执行某些操作以手动删除已使用SoftReferences的键值对清除---否则你的Map只会永远增长,并且泄漏记忆。

答案 2 :(得分:7)

要考虑的另一件事是,如果采用Map<K, WeakReference<V>>方法,该值可能会消失,但映射不会。根据使用情况,您最终可能会得到一个包含许多条目的地图,这些条目的弱引用已经过GC。

答案 3 :(得分:6)

您需要两个映射:一个映射在缓存键和weak referenced值之间,另一个映射在弱引用值和键之间的相反方向映射。你需要一个reference queue和一个清理线程。

弱引用能够在不再访问引用的对象时将引用移动到队列中。该队列必须由清理线程排干。 对于清理,有必要获取参考密钥。这就是为什么需要第二张地图的原因。

以下示例说明如何使用弱引用的哈希映射创建缓存。运行程序时,您将获得以下输出:

$ javac -Xlint:unchecked Cache.java && java Cache
{even: [2, 4, 6], odd: [1, 3, 5]}
{even: [2, 4, 6]}

第一行显示在删除对奇数列表的引用之前的缓存内容,以及删除赔率之后的第二行。

这是代码:

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

class Cache<K,V>
{
    ReferenceQueue<V> queue = null;
    Map<K,WeakReference<V>> values = null;
    Map<WeakReference<V>,K> keys = null;
    Thread cleanup = null;

    Cache ()
    {
        queue  = new ReferenceQueue<V>();
        keys   = Collections.synchronizedMap (new HashMap<WeakReference<V>,K>());
        values = Collections.synchronizedMap (new HashMap<K,WeakReference<V>>());
        cleanup = new Thread() {
                public void run() {
                    try {
                        for (;;) {
                            @SuppressWarnings("unchecked")
                            WeakReference<V> ref = (WeakReference<V>)queue.remove();
                            K key = keys.get(ref);
                            keys.remove(ref);
                            values.remove(key);
                        }
                    }
                    catch (InterruptedException e) {}
                }
            };
        cleanup.setDaemon (true);
        cleanup.start();
    }

    void stop () {
        cleanup.interrupt();
    }

    V get (K key) {
        return values.get(key).get();
    }

    void put (K key, V value) {
        WeakReference<V> ref = new WeakReference<V>(value, queue);
        keys.put (ref, key);
        values.put (key, ref);
    }

    public String toString() {
        StringBuilder str = new StringBuilder();
        str.append ("{");
        boolean first = true;
        for (Map.Entry<K,WeakReference<V>> entry : values.entrySet()) {
            if (first)
                first = false;
            else
                str.append (", ");
            str.append (entry.getKey());
            str.append (": ");
            str.append (entry.getValue().get());
        }
        str.append ("}");
        return str.toString();
    }

    static void gc (int loop, int delay) throws Exception
    {
        for (int n = loop; n > 0; n--) {
            Thread.sleep(delay);
            System.gc(); // <- obstinate donkey
        }
    }

    public static void main (String[] args) throws Exception
    {
        // Create the cache
        Cache<String,List> c = new Cache<String,List>();

        // Create some values
        List odd = Arrays.asList(new Object[]{1,3,5});
        List even = Arrays.asList(new Object[]{2,4,6});

        // Save them in the cache
        c.put ("odd", odd);
        c.put ("even", even);

        // Display the cache contents
        System.out.println (c);

        // Erase one value;
        odd = null;

        // Force garbage collection
        gc (10, 10);

        // Display the cache again
        System.out.println (c);

        // Stop cleanup thread
        c.stop();
    }
}