WeakHashMap de Java y almacenamiento en caché: ¿Por qué hace referencia a las claves, no a los valores?
caching weak-references (4)
El WeakHashMap de Java 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?
¿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 la objeto de cadena "some_key".
¿Me estoy perdiendo de algo?
El uso principal de WeakHashMap
es cuando tienes asignaciones que deseas desaparecer cuando desaparecen sus claves. Un caché es al revés: tienes asignaciones que deseas desaparecer cuando desaparecen sus valores.
Para un caché, lo que quiere es un Map<K,SoftReference<V>>
. A SoftReference
se le recogerá basura cuando la memoria se agota. (Contraste esto con una WeakReference
, que puede WeakReference
tan pronto como ya no haya una referencia dura a su referente.) Desea que sus referencias sean suaves en un caché (al menos en una donde las correlaciones de valores-clave no van 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 su comodidad, es posible que desee ocultar los valores de SoftReference
dentro de su implementación de Map
, de modo que su caché parezca ser del tipo <K,V>
lugar de <K,SoftReference<V>>
. Si desea hacer eso, esta pregunta tiene sugerencias para implementaciones disponibles en la red.
Tenga en cuenta también que cuando utiliza valores de SoftReference
en un Map
, debe hacer algo para eliminar manualmente los pares clave-valor a los que se les han SoftReferences
sus SoftReferences
; de lo contrario, su Map
crecerá en tamaño para siempre y perderá memoria.
Necesita dos mapas: uno que se correlacione entre la clave de caché y los valores de referencia débiles y otro en el mapeo de dirección opuesta entre los valores de referencia débil y las claves. Y necesita una cola de referencia y un hilo de limpieza.
Las referencias débiles tienen la capacidad de mover la referencia a una cola cuando el objeto al que se hace referencia no puede acceder 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 un caché con un mapa hash de referencias débiles. Cuando ejecuta el programa, 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 memoria caché antes de que se haya eliminado la referencia a la lista impar y la segunda línea después de que se hayan eliminado 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();
}
}
Otra cosa a considerar es que si toma el enfoque Map<K, WeakReference<V>>
, el valor puede desaparecer, pero el mapeo no. Dependiendo del uso, puede como resultado terminar con un mapa que contiene muchas entradas cuyas referencias débiles se han GC''d.
WeakHashMap no es útil como caché, al menos como lo piensa la mayoría de la gente. Como dices, usa claves débiles, no valores débiles, por lo que no está diseñado para lo que la mayoría de la gente quiere usar (y, de hecho, he visto a personas usarlo incorrectamente).
WeakHashMap es principalmente útil para mantener los metadatos sobre los objetos cuyo ciclo de vida 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 simple (y uno que he usado antes) podría ser algo como:
WeakHashMap<Thread, SomeMetaData>
donde puede hacer un seguimiento de lo que están haciendo varios hilos en su sistema; 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.
¡Mira WeakHashMap en no un caché! para más información.
Para el tipo de caché que está buscando, utilice un sistema de caché dedicado (por ejemplo, EHCache ) o consulte la clase MapMaker de google-collections'' ; algo como
new MapMaker().weakValues().makeMap();
hará lo que está buscando, o si quiere ser elegante, puede agregar caducidad temporizada:
new MapMaker().weakValues().expiration(5, TimeUnit.MINUTES).makeMap();