2009-11-26 10 views
55

Java's WeakHashMap a menudo se cita como útil para el almacenamiento en caché. Sin embargo, parece extraño que sus referencias débiles se definan en términos de las claves del mapa, no sus valores. Quiero decir, son los valores que quiero almacenar en la memoria caché, y los que quiero recoger basura una vez que nadie más, además de la memoria caché, hace referencia a ellos, ¿no?WeakHashMap y caché de Java: ¿Por qué hace referencia a las claves, no a los valores?

¿De qué manera ayuda mantener referencias débiles a las teclas? Si haces un ExpensiveObject o = weakHashMap.get("some_key"), entonces quiero que la memoria caché se mantenga en 'o' hasta que la persona que llama ya no tenga la referencia fuerte, y no me importa en absoluto el objeto de cadena "some_key".

¿Echo de menos algo?

+0

API de Java está llena de caprichos extraños. Siempre puede reescribir WeakHashMap usando WeakReference. – Pacerier

Respuesta

98

WeakHashMap no es útil como un caché, al menos como lo piensa la mayoría de la gente. Como dices, usa claves débiles, no valores, por lo que no está diseñado para lo que la mayoría de la gente quiere usar (y, de hecho, lo he usado personas lo usan incorrectamente).

WeakHashMap es sobre todo útil para mantener los metadatos de objetos cuyo ciclo de vida que no controlas. Por ejemplo, si tiene un grupo de objetos que pasan por su clase, y desea realizar un seguimiento de los datos adicionales sobre ellos sin necesidad de recibir notificaciones cuando se salgan del alcance, y sin su referencia para mantenerlos vivos.

Un ejemplo sencillo (y que he utilizado antes) podría ser algo como:

WeakHashMap<Thread, SomeMetaData> 

donde es posible realizar un seguimiento de lo hilos diferentes en su sistema están haciendo; cuando el hilo muere, la entrada se eliminará en silencio de su mapa y no evitará que el hilo sea recogido como basura si usted es la última referencia. Luego puede iterar sobre las entradas en ese mapa para averiguar qué metadatos tiene sobre los hilos activos en su sistema.

Consulte WeakHashMap in not a cache! para obtener más información.

Para el tipo de caché que está buscando, utilice un sistema de caché dedicado (por ejemplo, EHCache) o consulte google-collections'MapMaker class; algo así como

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

hará lo que usted está buscando, o si usted desea conseguir la suposición se puede añadir una hora de caducidad:

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

Sólo para actualizar esto para Ago 2013: Google Colecciones ahora lleva el nombre de guayaba, y la lógica de la creación de caché ahora es parte de la [CacheBuilder] (http://docs.guava-libraries.googlecode.com/git-history/release/ javadoc/index.html) clase. –

+0

enlace más preciso: http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/cache/CacheBuilder.html –

+1

nota, creo que en el ejemplo de Map Maker que eras significa decir nuevo MapMaker(). softValues ​​(). makeMap(), ya que llamar a weakValues ​​() le da el mismo resultado que un WeakHashMap. Hay un gran ejemplo aquí para ver cómo crear una memoria caché con Map Maker - http://stackoverflow.com/questions/3737140/use-of-google-collections-mapmaker – jklp

30

El principal uso de WeakHashMap es cuando se tiene asignaciones que desea para desaparecer cuando sus llaves desaparecen. Un caché es al revés: tienes asignaciones que deseas desaparecer cuando desaparecen sus valores.

Para una caché, lo que quiere es una Map<K,SoftReference<V>>. A SoftReference se recogerá basura cuando la memoria se comprime. (Contraste esto con un WeakReference, que puede borrarse tan pronto como ya no haya una referencia dura a su referente.) Desea que sus referencias sean suaves en un caché (al menos en uno donde las asignaciones de valores-clave no vayan obsoleto), dado que existe la posibilidad de que sus valores sigan en la memoria caché si los busca más adelante. Si las referencias fueran débiles en su lugar, sus valores se generarían de inmediato, lo que supondría un fracaso para el almacenamiento en caché.

Para mayor comodidad, es posible que desee ocultar los SoftReference valores dentro de su aplicación Map, para que su caché parece ser de tipo <K,V> en lugar de <K,SoftReference<V>>. Si desea hacer eso, this question tiene sugerencias para implementaciones disponibles en la red.

Tenga en cuenta también que cuando se utiliza SoftReference valores en un Map, que debe hacer algo para eliminar los pares de valores clave que han tenido su SoftReferences aclaró --- de lo contrario su Map perderá memoria.

+0

el uso de esta solución a lo largo del tiempo le deja con muchos elementos hashmap que el valor ha sido gc-ed. ¿Hay alguna alternativa que use un enfoque similar? –

+0

Un enfoque 'Map > 'deja instancias de' SoftReference' en el mapa que contienen referentes 'null' después de que se haya ejecutado GC. Creo que la implementación interna de este mapa debe borrar periódicamente todas las asignaciones con un valor que es una referencia suave que contiene un referente 'nulo', para una buena limpieza. – Timmos

+2

(continuación) Estrictamente hablando, si un programador ingenuo utiliza la implementación 'HashMap >', esto provocará una pérdida de memoria. Puede considerar incluir esto en su respuesta. Eche un vistazo a cómo lo hace 'WeakHashMap', el Oracle JDK tiene un método privado' expungeStaleEntries' que se ocupa de esta limpieza. – Timmos

6

Otro aspecto a considerar es que si se toma el enfoque Map<K, WeakReference<V>>, el valor puede desaparecer, pero el mapeo no lo hará. Dependiendo del uso, puede como resultado terminar con un mapa que contiene muchas entradas cuyas referencias débiles se han GC'd.

+0

'Mapa ', no 'Mapa >'. Esta respuesta parece tener sentido en la superficie, pero tenga en cuenta que las asignaciones faltantes se pueden eliminar cada vez que el usuario llama a 'Map.get', y viendo que [esto es exactamente cómo WeakHashMap elimina las claves] (http://archive.is/ Z3aK9 # selection-1069.117-1069.237), no podría haber sido que el equipo de Java no se dio cuenta de esto. – Pacerier

6

Necesita dos mapas: uno que se correlacione entre la clave de caché y los valores weak referenced y otro en el mapeo de dirección opuesta entre los valores de referencia débil y las teclas. Y necesita un reference queue y un hilo de limpieza.

referencias débiles tienen la capacidad de mover la referencia en una cola cuando el objeto referenciado no puede visitada por más tiempo. Esta cola debe ser drenada por un hilo de limpieza. Y para la limpieza, es necesario obtener la clave para una referencia. Esta es la razón por la cual se requiere el segundo mapa.

El siguiente ejemplo muestra cómo crear una memoria caché con un mapa hash de referencias débiles. Al ejecutar el programa se obtiene el siguiente resultado:

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

La primera línea muestra el contenido de la caché antes se ha suprimido la referencia a la lista extraña y se han eliminado la segunda línea después de las probabilidades.

Este es el código:

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

Gran respuesta. Vale la pena señalar que una ReferenceQueue, a diferencia de muchos otros tipos de colección, bloquea hasta que se pueda devolver un valor desde queue.remove(). Esto significa que el hilo de limpieza no es el ciclo infinito sin esperar que pueda sugerir el primer vistazo. –

+0

@Gordon Si utiliza claves débiles y valores débiles, todo es débil y se recolectará la basura justo después de haberla agregado a la memoria caché. – ceving

+0

** Esta es la respuesta **. Entonces podemos decir que, por lo tanto, ** optimizar la fase de limpieza ** es que la API se implementa como tal. – Pacerier

Cuestiones relacionadas