Guide de WeakHashMap en Java

1. Vue d'ensemble

Dans cet article, nous examinerons un WeakHashMap du package java.util .

Afin de comprendre la structure des données, nous allons l'utiliser ici pour déployer une implémentation de cache simple. Cependant, gardez à l'esprit que cela vise à comprendre le fonctionnement de la carte et que créer votre propre implémentation de cache est presque toujours une mauvaise idée.

En termes simples, WeakHashMap est une implémentation basée sur une table de hachage de l' interface Map , avec des clés de type WeakReference .

Une entrée dans un WeakHashMap sera automatiquement supprimée lorsque sa clé n'est plus utilisée normalement , ce qui signifie qu'il n'y a pas de référence unique qui pointe vers cette clé. Lorsque le processus de garbage collection (GC) rejette une clé, son entrée est effectivement supprimée de la carte, de sorte que cette classe se comporte quelque peu différemment des autres implémentations de Map .

2. Références fortes, souples et faibles

Pour comprendre comment fonctionne WeakHashMap , nous devons examiner une classe WeakReference - qui est la construction de base pour les clés dans l' implémentation WeakHashMap . En Java, nous avons trois principaux types de références, que nous expliquerons dans les sections suivantes.

2.1. Références fortes

La référence forte est le type de référence le plus courant que nous utilisons dans notre programmation quotidienne:

Integer prime = 1;

La variable prime a une forte référence à un objet Integer avec la valeur 1. Tout objet qui a une forte référence pointant vers lui n'est pas éligible pour GC.

2.2. Références souples

En termes simples, un objet qui a une SoftReference pointant vers lui ne sera pas récupéré jusqu'à ce que la JVM ait absolument besoin de mémoire.

Voyons comment nous pouvons créer une SoftReference en Java:

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

L' objet principal a une référence forte qui pointe vers lui.

Ensuite, nous enveloppons la référence forte principale dans une référence souple. Après avoir rendu cette référence forte nulle , un objet principal est éligible pour GC mais ne sera collecté que lorsque JVM a absolument besoin de mémoire.

2.3. Références faibles

Les objets référencés uniquement par des références faibles sont récupérés avec empressement; le GC n'attendra pas d'avoir besoin de mémoire dans ce cas.

Nous pouvons créer une WeakReference en Java de la manière suivante:

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

Lorsque nous avons fait une référence principale nulle , l' objet principal sera récupéré dans le prochain cycle GC, car il n'y a aucune autre référence forte pointant vers lui.

Les références de type WeakReference sont utilisées comme clés dans WeakHashMap .

3. WeakHashMap comme cache mémoire efficace

Disons que nous voulons créer un cache qui conserve les grands objets image comme valeurs et les noms d'image comme clés. Nous voulons choisir une implémentation de carte appropriée pour résoudre ce problème.

Utiliser un simple HashMap ne sera pas un bon choix car les objets de valeur peuvent occuper beaucoup de mémoire. De plus, ils ne seront jamais récupérés du cache par un processus GC, même s'ils ne sont plus utilisés dans notre application.

Idéalement, nous voulons une implémentation Map qui permette à GC de supprimer automatiquement les objets inutilisés. Lorsqu'une clé d'un objet de grande image n'est pas utilisée dans notre application à aucun endroit, cette entrée sera supprimée de la mémoire.

Heureusement, le WeakHashMap a exactement ces caractéristiques. Testons notre WeakHashMap et voyons comment il se comporte:

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);

Nous créons une instance WeakHashMap qui stockera nos objets BigImage . Nous mettons un objet BigImage comme valeur et une référence d'objet imageName comme clé. L' imageName sera stocké dans une carte en tant que type WeakReference .

Ensuite, nous définissons la référence imageName sur null , il n'y a donc plus de références pointant vers l' objet bigImage . Le comportement par défaut d'un WeakHashMap est de récupérer une entrée qui n'a aucune référence à elle sur le prochain GC, donc cette entrée sera supprimée de la mémoire par le processus GC suivant.

Nous appelons un System.gc () pour forcer la JVM à déclencher un processus GC. Après le cycle GC, notre WeakHashMap sera vide:

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));

Notez que seule la référence imageNameFirst est définie sur null . La référence imageNameSecond reste inchangée. Une fois GC déclenché, la carte ne contiendra qu'une seule entrée - imageNameSecond .

4. Conclusion

Dans cet article, nous avons examiné les types de références en Java pour comprendre pleinement comment java.util. WeakHashMap fonctionne. Nous avons créé un cache simple qui exploite le comportement d'un WeakHashMap et teste s'il fonctionne comme prévu.

L'implémentation de tous ces exemples et extraits de code se trouve dans le projet GitHub - qui est un projet Maven, il devrait donc être facile à importer et à exécuter tel quel.