Guida a WeakHashMap in Java

1. Panoramica

In questo articolo, esamineremo una WeakHashMap dal pacchetto java.util .

Per comprendere la struttura dei dati, la useremo qui per implementare una semplice implementazione della cache. Tuttavia, tieni presente che questo ha lo scopo di capire come funziona la mappa e creare la tua implementazione della cache è quasi sempre una cattiva idea.

In poche parole, WeakHashMap è un'implementazione basata su tabella hash dell'interfaccia Map , con chiavi di tipo WeakReference .

Una voce in una WeakHashMap verrà automaticamente rimossa quando la sua chiave non è più in uso normale, il che significa che non esiste un singolo riferimento che punta a quella chiave. Quando il processo di Garbage Collection (GC) scarta una chiave, la sua voce viene effettivamente rimossa dalla mappa, quindi questa classe si comporta in modo leggermente diverso dalle altre implementazioni di Map .

2. Riferimenti forti, morbidi e deboli

Per capire come funziona WeakHashMap , dobbiamo esaminare una classe WeakReference , che è il costrutto di base per le chiavi nell'implementazione di WeakHashMap . In Java, abbiamo tre tipi principali di riferimenti, che spiegheremo nelle sezioni seguenti.

2.1. Riferimenti forti

Il riferimento forte è il tipo più comune di riferimento che utilizziamo nella nostra programmazione quotidiana:

Integer prime = 1;

La variabile primo ha un forte riferimento a un oggetto Integer con valore 1. Qualsiasi oggetto che ha un forte riferimento che punta ad esso non è idoneo per GC.

2.2. Riferimenti morbidi

In poche parole, un oggetto che ha un SoftReference che punta ad esso non verrà raccolto in modo indesiderato finché la JVM non avrà assolutamente bisogno di memoria.

Vediamo come possiamo creare un SoftReference in Java:

Integer prime = 1; SoftReference soft = new SoftReference(prime); prime = null;

L' oggetto principale ha un forte riferimento che lo punta.

Successivamente, stiamo avvolgendo un riferimento forte primo in un riferimento morbido. Dopo aver reso nullo quel riferimento forte , un oggetto principale è idoneo per GC ma verrà raccolto solo quando JVM ha assolutamente bisogno di memoria.

2.3. Riferimenti deboli

Gli oggetti a cui fanno riferimento solo riferimenti deboli vengono raccolti in modo impaziente; il GC non aspetterà finché non avrà bisogno di memoria in quel caso.

Possiamo creare un riferimento debole in Java nel modo seguente:

Integer prime = 1; WeakReference soft = new WeakReference(prime); prime = null;

Quando rendiamo nullo un riferimento primo , l' oggetto primo verrà raccolto in modo indesiderato nel ciclo GC successivo, poiché non vi è alcun altro riferimento forte che lo punti.

I riferimenti di un tipo WeakReference vengono utilizzati come chiavi in WeakHashMap .

3. WeakHashMap come cache di memoria efficiente

Supponiamo di voler costruire una cache che mantenga gli oggetti immagine grandi come valori e i nomi delle immagini come chiavi. Vogliamo scegliere una corretta implementazione della mappa per risolvere il problema.

Usare una semplice HashMap non sarà una buona scelta perché gli oggetti valore possono occupare molta memoria. Inoltre, non verranno mai recuperati dalla cache da un processo GC, anche quando non sono più in uso nella nostra applicazione.

Idealmente, vogliamo un'implementazione della mappa che consenta a GC di eliminare automaticamente gli oggetti inutilizzati. Quando una chiave di un grande oggetto immagine non è in uso nella nostra applicazione da nessuna parte, quella voce verrà cancellata dalla memoria.

Fortunatamente, WeakHashMap ha esattamente queste caratteristiche. Testiamo la nostra WeakHashMap e vediamo come si comporta:

WeakHashMap map = new WeakHashMap(); BigImage bigImage = new BigImage("image_id"); UniqueImageName imageName = new UniqueImageName("name_of_big_image"); map.put(imageName, bigImage); assertTrue(map.containsKey(imageName)); imageName = null; System.gc(); await().atMost(10, TimeUnit.SECONDS).until(map::isEmpty);

Stiamo creando un'istanza WeakHashMap che memorizzerà i nostri oggetti BigImage . Stiamo inserendo un oggetto BigImage come valore e un riferimento all'oggetto imageName come chiave. L'imageName verrà memorizzato in una mappa come WeakReference tipo.

Successivamente, impostiamo il riferimento imageName su null , quindi non ci sono più riferimenti che puntano all'oggetto bigImage . Il comportamento predefinito di una WeakHashMap consiste nel recuperare una voce che non ha alcun riferimento ad essa nel GC successivo, quindi questa voce verrà eliminata dalla memoria dal processo GC successivo.

Stiamo chiamando un System.gc () per forzare la JVM ad attivare un processo GC. Dopo il ciclo GC, la nostra WeakHashMap sarà vuota:

WeakHashMap map = new WeakHashMap(); BigImage bigImageFirst = new BigImage("foo"); UniqueImageName imageNameFirst = new UniqueImageName("name_of_big_image"); BigImage bigImageSecond = new BigImage("foo_2"); UniqueImageName imageNameSecond = new UniqueImageName("name_of_big_image_2"); map.put(imageNameFirst, bigImageFirst); map.put(imageNameSecond, bigImageSecond); assertTrue(map.containsKey(imageNameFirst)); assertTrue(map.containsKey(imageNameSecond)); imageNameFirst = null; System.gc(); await().atMost(10, TimeUnit.SECONDS) .until(() -> map.size() == 1); await().atMost(10, TimeUnit.SECONDS) .until(() -> map.containsKey(imageNameSecond));

Notare che solo il riferimento imageNameFirst è impostato su null . Il riferimento imageNameSecond rimane invariato. Dopo che GC è stato attivato, la mappa conterrà solo una voce: imageNameSecond .

4. Conclusione

In questo articolo, abbiamo esaminato i tipi di riferimenti in Java per comprendere appieno come java.util. WeakHashMap funziona. Abbiamo creato una semplice cache che sfrutta il comportamento di una WeakHashMap e verifica se funziona come previsto.

L'implementazione di tutti questi esempi e frammenti di codice può essere trovata nel progetto GitHub, che è un progetto Maven, quindi dovrebbe essere facile da importare ed eseguire così com'è.