web-dev-qa-db-ja.com

Java WeakHashMapとキャッシング:なぜ値ではなくキーを参照するのですか?

Javaの WeakHashMap は、キャッシュに役立つとよく言われます。ただし、その弱い参照は、値ではなくマップのキーの観点から定義されているのは奇妙に思えます。つまり、キャッシュしたい値であり、キャッシュ以外に誰もそれらを強く参照していないときにガベージコレクションを取得したいのですか?

どの方法でキーへの弱い参照を保持するのに役立ちますか? ExpensiveObject o = weakHashMap.get("some_key")を実行すると、呼び出し元が強参照を保持しなくなるまでキャッシュを 'o'に保持し、文字列オブジェクト "some_keyについてはまったく気にしません「。

何か不足していますか?

63
Matthias

WeakHashMap is n'tキャッシュとしては、少なくともほとんどの人が考える方法としては便利です。あなたが言うように、それは弱いkeysではなく、弱いvaluesを使用するので、ほとんどの人がそれを使用したいもののために設計されていません(そして実際、私は- seen人々はそれを間違って使用します)。

WeakHashMapは、ライフサイクルを制御できないオブジェクトに関するメタデータを保持するのに最も役立ちます。たとえば、クラスを通過するオブジェクトの束があり、それらがスコープ外になったときに通知される必要なく、それらのオブジェクトへの参照が生き続けるためにそれらについての追加データを追跡したい場合。

簡単な例(および以前に使用した例)は次のようなものです。

WeakHashMap<Thread, SomeMetaData>

システム内のさまざまなスレッドが何をしているかを追跡できる場所。スレッドが終了すると、エントリはマップからサイレントに削除され、最後の参照である場合はスレッドがガベージコレクションされないようにすることはできません。その後、そのマップ内のエントリを反復処理して、システム内のアクティブなスレッドについてどのようなメタデータがあるかを確認できます。

詳細については、 キャッシュではなくWeakHashMap! を参照してください。

使用するキャッシュのタイプについては、専用のキャッシュシステムを使用するか(例 EHCache )、または google-collections 'MapMakerクラス をご覧ください。 ;何かのようなもの

new MapMaker().weakValues().makeMap();

あなたが望んでいることをします、または空想を得たい場合は、期限切れの期限を追加できます

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

WeakHashMapの主な用途は、キーが消えたときに消えるマッピングがある場合です。キャッシュは逆です---値が消えたときに消したいマッピングがあります。

キャッシュの場合、必要なのはMap<K,SoftReference<V>>SoftReference は、メモリが不足するとガベージコレクションされます。 (これとは対照的に、WeakReferenceは、その参照先へのハード参照がなくなるとすぐにクリアされる可能性があります。)キャッシュ内で参照をソフトにしたい(少なくともキーと値のマッピングが行われない場合)失効)、それ以降、後で検索した場合、値がキャッシュに残っている可能性があります。代わりに参照が弱い場合、値はすぐにgcされ、キャッシュの目的が無効になります。

便宜上、SoftReference実装内でMap値を非表示にして、キャッシュが<K,V> の代わりに <K,SoftReference<V>>。それをしたい場合は、 この質問 には、ネット上で利用可能な実装に関する提案があります。

また、SoftReferenceMapの値を使用する場合、mustを実行して、SoftReferencesがクリアされたキーと値のペアを手動で削除する必要があります---そうしないと、Mapのサイズが永遠に大きくなり、メモリリークが発生します。

34
uckelman

考慮すべきもう1つのことは、Map<K, WeakReference<V>>アプローチでは、値は消えますが、マッピングは消えません。使用法によっては、結果として、弱い参照がGCされた多くのエントリを含むマップになる場合があります。

7
Shannon

2つのマップが必要です。1つはキャッシュキーと 弱い参照 値の間をマップし、もう1つは弱い参照値とキーの間を逆方向にマッピングします。そして、 参照キュー とクリーンアップスレッドが必要です。

弱参照には、参照オブジェクトがアクセスできなくなったときに参照をキューに移動する機能があります。このキューは、クリーンアップスレッドによって排出される必要があります。 そして、クリーンアップのために、参照用のキーを取得する必要があります。これが、2番目のマップが必要な理由です。

次の例は、弱参照のハッシュマップを使用してキャッシュを作成する方法を示しています。プログラムを実行すると、次の出力が得られます。

 $ javac -Xlint:unchecked Cache.Java && Java Cache 
 {even:[2、4、6]、odd:[1、3、5 ]} 
 {偶数:[2、4、6]} 

最初の行は、奇数リストへの参照が削除される前のキャッシュの内容と、オッズが削除された後の2行目を示しています。

これはコードです:

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();
    }
}
6
ceving