• Références fortes (strong reference)
  • Références faibles (weak reference)
  • Références douces (soft reference)
  • Comment mettre en place un «cache» en Java ?

tassecafe-fillebrascroise.png

Références fortes (strong reference)

Une référence forte en Java est une référence, bien connue, créée comme suit

MyObject mo = new MyObject();

Tant que ma variable conserve une référence vers l'objet, celui-ci ne sera pas éligible par le garbage collector [1] (Wikipedia). L'objet reste donc toujours en mémoire.

Gérer sa mémoire en Java consiste simplement à gérer ses variables. En Java j'ai assez peu de chances d'avoir des problèmes de fuite mémoire (memory leak) ou de pertes d'objets (damned ! J'ai détruit un objet dont j'avais encore besoin). Ceci est d'ailleurs vrai pour tous les langages utilisant un garbage collector.

Si je voulais gérer un cache pour des images en utilisant les références fortes (habituelles) je devrais

  • choisir une taille pour mon cache
  • sauvegarder des images dans un container quelconque (un HashMap par exemple)
  • décider de supprimer certaines images (lesquelles ?) lorsque je veux en ajouter d'autres
  • choisir une politique de conservation dans le cache (le plus souvent utilisé, les x derniers, ...)

... tout ceci me semble assez «manuel» (et donc fastidieux).

Références faibles (weak reference)

Java définit la notion de référence faible (weak reference), dans sa javadoc de la classe WeakReference, comme

Définition Une référence faible est une référence qui n'est pas suffisament forte pour forcer un objet à rester en mémoire suite au passage du garbage collector

Si l'on suppose l'existence d'une classe MyObject représentant un «objet quelconque», on pourra l'utiliser comme suit[2]:

WeakReference<MyObject> wr = new WeakReference<>(
    new MyObject(//params//));
wr.get();

L'object sera disponible un certain temps et si le garbage collector estime qu'il peut le récupérer, il le fera. Dans ce cas, la méthode get retournera null. Il doit être clair, qu'il ne doit plus y avoir de référence forte vers cet objet

import java.lang.ref.WeakReference;
 
public class TestWeakReference { 
   public static void main ( String[] args ) { 
      WeakReference<MyObject> wr = new WeakReference<>(
        new MyObject(5, "description"));
      // Il faut gérer les références fortes
      // MyObject mo = wr.get();
      for (int i=0; i<10; i++) {
         System.out.println(wr.get());		 
         if (i==5) System.gc();
      }
   }
}

On obtient les résultats suivant suivant que l'on décommente ou pas la ligne.

screenshot-weakref-1.png

screenshot-weakref-2.png

On peut mettre en œuvre un exemple un peu plus parlant. Je vais utiliser une classe permettant de réserver un certain nombre de bytes en mémoire. Nous allons utiliser une classe MemoryBlock (l'idée provient de Alexandre Pereira Calsavara ) telle que

public class MemoryBlock {
   private int id;
   private int size;
   private byte[] block;
 
   public MemoryBlock( int id, int size ) {
      this.id = id;
      this.size = size;
      block = new byte[size];
      System.out.println("MemoryBlock created: " + this);
   }
 
   @Override
   public String toString() {
      return "{id="+id+",size="+size+"}";
   }
 
   @Override
   // Méthode appelée lorsque l'on libère l'objet
   protected void finalize() {
      System.out.println( "MemoryBlock finalized: "+this );
   }
}

Je vais maintenant créer des blocs de plus en plus grands jusqu'à ce que le garbage collector décide de libérer de l'espace mémoire.

import java.lang.ref.WeakReference;
import java.lang.ref.Reference;
import java.util.List;
import java.util.ArrayList;
 
public class TestWeakReferenceMemoryBlock { 
	 public static void main ( String[] args ) { 
		 List<Reference<MemoryBlock>> list = 
		 	new ArrayList<>();
		 int size = 65536;
		 int id = 0;
		 while (true) {
			 list.add(new WeakReference<>(
                                new MemoryBlock(id++, size)));
			 size *= 2;
			 display(list);
			 System.gc();			 	
		 }
	 }
 
	 public static void display(List<Reference<MemoryBlock>> list){
		 for (Reference<MemoryBlock> sr : list) {
			 System.out.println("" + sr.get());
		 }
	 }
}

screenshot-weakref-4.png

Remarque

Ôter l'appel explicite au garbage collector induit une OutOfMemoryError :-(

Références douces soft reference

La différence entre une référence douce et une référence faible réside dans le fait que la libération de l'espace mémoire est maintenant laissé à la discrétion du garbage collector en fonction de l'espace mémoire dont il dispose.

Si je %s/WeakReference/SoftReference/ dans l'exemple précédent, je m'attend à ce que la libération des objets se fasse plus tard.

En lisant la littérature, je vois que l'objet ReferenceQueue me permet d'obtenir une liste des objets candidats à l'exécution par le garbage collector. Les objets de cette liste peuvent donc être retirés de ma liste d'objets.

import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.Reference;
import java.util.List;
import java.util.ArrayList;
 
public class TestSoftReferenceQueue { 
	 public static void main ( String[] args ) { 
		 ReferenceQueue<MemoryBlock> queue =  
                          new ReferenceQueue<>();
		 List<Reference<MemoryBlock>> list = 
		 	new ArrayList<>();
		 int size = 65536;
		 int id = 0;
		 Reference<MemoryBlock> reference;
		 while (true) {
			 reference = new SoftReference<>(
                             new MemoryBlock(id++, size), queue);
			 list.add(reference);
			 size *= 2;
			 display(list);
			 System.gc();
			 Reference<MemoryBlock> sr;
			 while ((sr=queue.poll()) != null) {
				 list.remove(sr);
			 }
		 }
	 }

Remarque

Cet exemple n'est pas concluant. J'obtiens une OutOfMemoryError et aucun objet n'est libéré ... reste à voir si je peux mettre en œuvre ce cache dont on parle et si l'objet WeakHashMap répond à mes attentes. Suspense !

Comment mettre en place un «cache» en Java ?

L'objet WeakHashMap est une table (map) dont les clés sont des références faibles (weak références) et telle que lorsque la référence n'est plus disponible, l'entrée est supprimée du map.

Hash table based implementation of the Map interface, with weak keys. An entry in a WeakHashMap will automatically be removed when its key is no longer in ordinary use. More precisely, the presence of a mapping for a given key will not prevent the key from being discarded by the garbage collector, that is, made finalizable, finalized, and then reclaimed. When a key has been discarded its entry is effectively removed from the map, so this class behaves somewhat differently from other Map implementations.

Extrait de l'API Java

Avec cet objet, implémenter un cache est très simple. Je ne dois pas m'inquiéter de la taille de mon cache, c'est Java qui va la gérer en fonction de la mémoire dont il dispose. Lorsque j'ai besoin d'un objet,

  • je regarde s'il est présent dans le map,
  • si oui, je l'utilise
  • si non, je le charge et l'ajoute dans le map pour plus tard

Soit le code suivant permettant de tester l'utilisation de WeakHashMap pour créer facilement un cache. Je crée des blocs que j'ajoute dans le cache et, à chaque création, j'affiche le contenu du chache histoire de voir des blocs sont supprimés du cache.

import java.util.WeakHashMap;
 
public class TestWeakHashMap2 { 
	private static WeakHashMap<String,MemoryBlock> cache = 
		new WeakHashMap<>();
	private static int n = 0;
 
	public static void main ( String[] args ) {
		int size = 6*65536;
		while (n<50) {
			cache.put(""+n++, new MemoryBlock(n, size));
			System.out.printf("Keys in cache: ");
			for (String s : cache.keySet()) {
				System.out.printf(" %s ", s);
			}
			System.out.printf("\n");
		}
	}
}

Et l'on constate qu'à partir d'un certain moment, des blocs disparaissent du cache (et ceci sans faire passer explicitement le garbage collector). Le test est donc assez concluant.

screenshot-weakref-5.png

screenshot-weakref-6.png

Hormis les deux remarques concernant le garbage collector et les références souples (soft reference), les tests sont assez concluants.

Si l'on se réfère à l'introduction, nous pouvons maintenant nous présenter sans soucis à un entretien d'embauche et répondre que l'on sait ce que sont les références faibles (weak reference)[3]

Enjoy !

Sources

À (re)lire aussi

Notes

[1] Dans la suite, je ne dirai jamais le ramasse miettes ;-)

[2] Les codes sont «à partir de JDK7»

[3] Si vous suivez les liens, vous verrez que l'on parle également de références fantômes (phantom reference) dont je n'ai pas parlé dans l'article.