2009-11-26 5 views
55

Javas WeakHashMap wird oft als nützlich für das Zwischenspeichern zitiert. Es erscheint jedoch seltsam, dass seine schwachen Referenzen in Bezug auf die Schlüssel der Karte definiert sind, nicht ihre Werte. Ich meine, es sind die Werte, die ich cachen möchte und die ich sammeln möchte, wenn niemand außer dem Cache sie stark referenziert, nein?Javas WeakHashMap und Caching: Warum referenziert es die Schlüssel, nicht die Werte?

Inwiefern hilft es, schwache Referenzen auf die Schlüssel zu halten? Wenn Sie eine ExpensiveObject o = weakHashMap.get("some_key") machen, dann möchte ich, dass der Cache auf "o" bleibt, bis der Aufrufer nicht mehr die starke Referenz hält, und mir ist überhaupt nichts von dem String-Objekt "some_key" wichtig.

Fehle ich etwas?

+0

Java API voll von seltsamen Macken ist. Sie können WeakHashMap immer mit WeakReference neu schreiben. – Pacerier

Antwort

98

WeakHashMap ist nicht nützlich als Cache, zumindest die Art, wie die meisten Leute darüber denken. Wie Sie sagen, es verwendet schwach Schlüssel, nicht schwach Werte, so ist es nicht für das, was die meisten Menschen es für verwenden wollen (und in der Tat habe ich gesehen Menschen verwenden es für, falsch).

WeakHashMap ist hauptsächlich nützlich, um Metadaten über Objekte zu behalten, deren Lebenszyklus Sie nicht kontrollieren. Zum Beispiel, wenn Sie eine Menge Objekte durch Ihre Klasse passieren lassen und Sie zusätzliche Daten über sie verfolgen möchten, ohne dass Sie benachrichtigt werden müssen, wenn sie den Bereich verlassen, und ohne dass Sie sich darauf beziehen, um sie am Leben zu erhalten.

Ein einfaches Beispiel (und ein Ich habe, bevor sie verwendet) könnten sein, so etwas wie:

WeakHashMap<Thread, SomeMetaData> 

wo Sie den Überblick von dem, was in Ihrem System verschiedene Fäden halten könnten tun; Wenn der Thread abstirbt, wird der Eintrag automatisch aus der Map entfernt und der Thread wird nicht mehr als Garbage Collection behandelt, wenn Sie der letzte Hinweis darauf sind. Sie können dann über die Einträge in dieser Map iterieren, um herauszufinden, welche Metadaten Sie über aktive Threads in Ihrem System haben.

Weitere Informationen finden Sie unter WeakHashMap in not a cache!.

Für den Cache-Typ, den Sie suchen, verwenden Sie entweder ein dediziertes Cache-System (z. B. EHCache) oder sehen Sie sich google-collections' an. MapMaker class; so etwas wie

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

wird tun, was Sie nach, oder wenn Sie Lust bekommen möchten, können Sie zeitlich Ablauf hinzufügen:

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

Um dies für August 2013 zu aktualisieren: Google Collections heißt jetzt Guava und die Cache-Erstellungslogik ist nun Teil des [CacheBuilder] (http://docs.guava-libraries.googlecode.com/githistory/release/ javadoc/index.html) Klasse. –

+0

Genauerer Link: http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/cache/CacheBuilder.html –

+1

Hinweis, ich denke in Ihrem Beispiel für MapMaker waren Sie soll sagen MapMaker(). softValues ​​(). makeMap(), als Aufruf von weakValues ​​() gibt Ihnen das gleiche Ergebnis wie eine WeakHashMap. Es gibt ein großartiges Beispiel dafür, wie man mit MapMaker einen Cache erstellt - http://StackOverflow.com/questions/3737140/use-of-google-collections-mapmaker – jklp

30

Die Hauptanwendung für WeakHashMap ist, wenn Sie Zuordnungen, die Sie wollen verschwinden, wenn ihre Schlüssel verschwinden. Ein Cache ist die Umkehrung - Sie haben Zuordnungen, die Sie verschwinden lassen wollen, wenn ihre Werte verschwinden.

Für einen Cache, was Sie wollen, ist ein Map<K,SoftReference<V>>. A SoftReference wird Müll gesammelt werden, wenn Speicher knapp wird. (Vergleichen Sie dies mit einem WeakReference, das gelöscht werden kann, sobald es keine feste Referenz mehr auf seinen Referenten gibt.) Sie möchten, dass Ihre Referenzen in einem Cache weich sind (zumindest in einem, in dem Schlüsselwertzuordnungen nicht gehen) veraltet), da dann die Chance besteht, dass Ihre Werte im Cache verbleiben, wenn Sie später danach suchen. Wenn die Referenzen stattdessen schwach wären, würden Ihre Werte sofort erkannt und der Zweck des Cachings vereitelt.

Der Einfachheit halber sollten Sie die SoftReference Werte in Ihrem Map Implementierung verstecken, so dass Sie den Cache vom Typ zu sein scheint <K,V> statt <K,SoftReference<V>>. Wenn Sie das tun möchten, hat this question Vorschläge für im Netz verfügbare Implementierungen.

Beachten Sie auch, dass, wenn Sie SoftReference Werte in einem Map verwenden, Sie etwas tun müssen, Schlüssel-Wert-Paare zu entfernen, die ihre SoftReferences gelöscht hatten --- sonst Ihre Map leckt Speicher.

+0

mit dieser Lösung im Laufe der Zeit verlassen Sie mit vielen hashmap Elemente, die der Wert wurde gc-ed. Gibt es eine Alternative, die einen ähnlichen Ansatz verfolgt? –

+0

Ein 'Map >' Ansatz lässt Blätter Instanzen von 'SoftReference' in der Zuordnung, die' null'-Referenzen enthalten, nachdem GC ausgeführt wurde. Ich denke, dass die interne Implementierung dieser Zuordnung regelmäßig alle Zuordnungen mit einem Wert löschen muss, der eine weiche Referenz ist, die einen "null" Referent für eine nette Bereinigung enthält. – Timmos

+2

(Fortsetzung) Genau genommen, wenn ein naiver Programmierer die Implementierung 'HashMap >' verwendet, dann wird dies zu einem Speicherleck führen. Sie könnten darüber nachdenken, dies in Ihre Antwort aufzunehmen. Schauen Sie sich an, wie 'WeakHashMap' das macht, das Oracle JDK hat eine private Methode' expungeStaleEntries', die sich um diese Bereinigung kümmert. – Timmos

6

Eine andere Sache zu berücksichtigen ist, dass wenn Sie die Map<K, WeakReference<V>> Ansatz nehmen, der Wert möglicherweise verschwindet, aber die Zuordnung wird nicht. Abhängig von der Verwendung können Sie als Ergebnis eine Karte mit vielen Einträgen erhalten, deren schwache Referenzen ausgewertet wurden.

+0

'Karte ', nicht 'Map >'. Diese Antwort scheint auf der Oberfläche Sinn zu ergeben, aber beachte, dass die fehlenden Zuordnungen jedes Mal entfernt werden können, wenn der Benutzer 'Map.get' aufruft und dass [genau so entfernt WeakHashMap die Schlüssel] (http://archive.is/ Z3aK9 # selection-1069.117-1069.237), es konnte nicht sein, dass das Java-Team das nicht erkannt hatte. – Pacerier

6

Sie benötigen zwei Karten: eine Karte, die zwischen dem Cache-Schlüssel und weak referenced Werte und eine in der entgegengesetzten Richtung Zuordnung zwischen den schwach referenzierten Werten und den Schlüsseln. Und Sie brauchen eine reference queue und einen Aufräumfaden.

Schwache Referenzen können den Verweis in eine Warteschlange verschieben, wenn auf das referenzierte Objekt nicht mehr zugegriffen werden kann. Diese Warteschlange muss von einem Bereinigungsthread entleert werden. Und für die Reinigung ist nötig es den Schlüssel für die Referenz zu bekommen. Dies ist der Grund, warum die zweite Karte benötigt wird.

Das folgende Beispiel zeigt, wie ein Cache mit einer Hash-Map mit schwachen Referenzen erstellt wird. Wenn Sie das Programm ausführen erhalten Sie die folgende Ausgabe:

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

Die erste Zeile den Inhalt des Cache zeigt, bevor der Verweis auf die ungeraden Liste gelöscht wurde und die zweite Zeile, nachdem die Quoten gelöscht wurden.

Dies ist der Code:

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(); 
    } 
} 
+1

Große Antwort. Beachten Sie, dass eine ReferenceQueue im Gegensatz zu vielen anderen Arten der Auflistung blockiert, bis ein Wert von queue.remove() zurückgegeben werden kann. Dies bedeutet, dass der Aufräum-Thread nicht die nicht-wartende Endlos-Schleife ist, die der erste Blick vermuten lässt. –

+0

@Gordon Wenn Sie schwache Schlüssel und schwache Werte verwenden, ist alles schwach und es wird nur Müll gesammelt, nachdem Sie es dem Cache hinzugefügt haben. – ceving

+0

** Dies ist die Antwort **. Daher können wir sagen, dass es für die Optimierung der Säuberungsphase ** gilt, dass die API als solche implementiert ist. – Pacerier

Verwandte Themen