Il y a quelques temps j’ai dû interviewer des candidats pour un poste de d’ingénieur Java. L’une des questions que je leur posais était « que pouvez vous me dire à propos des références faibles? ». Je n’attendais pas de détails techniques. J’aurais probablement été satisfait par un « hum… est ce que ça n’aurait pas à voir avec le ramasse-miettes? ». À la place, j’ai été surpris de voir que sur les 20 ingénieurs postulants, tous ayant au moins 5 ans d’expérience dans le développement Java et de bonnes qualifications, seulement deux savaient que les références faibles existaient, et un seul parmi ces deux avait une connaissance pratique du sujet. [...] J’ignore pourquoi cette connaissance est aussi peu répandue, dans la mesure où les références faibles sont incontournables depuis la sortie de Java 1.2, il y a 7 ans de cela.

Source: Understanding Weak References de Ethan Nicholas

Introduction 2 en 1: à la fois instructive tout en présentant l’intérêt d’excuser par avance l’ignorance de l’auteur de ce blog. Je ne vais pas rappeler ici l’intérêt du ramasse-miettes (garbage collector) ainsi que le concept de compteur de références: vous pourrez retrouver ces informations dans l’introduction de l’article que j’avais écrit sur le fonctionnement du garbage collector d’ActionScript. En effet, les fondamentaux étant les mêmes en ce qui concerne son fonctionnement, les pratiques induisant des fuites mémoire sont également identiques: elles concernent principalement la gestion des écouteurs ainsi que celle des tables de hachage.

Les références faibles.
Des références faibles peuvent être créées à l’aide des classes WeakReference et SoftReference. Toutes deux héritent de la classe Reference et s’utilisent de la même manière. Ainsi, pour manipuler une WeakReference vous devez procéder de la manière suivante:

// donnée à faiblement référencer 
Object o1 = new Object();
 
// création de la référence faible
WeakReference ref = new WeakReference(o1);
 
// récupération de la valeur faiblement référencée
Object o2 = ref.get();
 
boolean isEqual = o1 == o2; // isEqual = true

Une fois créé, on peut accéder à l’objet faiblement référencé à l’aide de la méthode get(). Dans le cas de l’utilisation d’une WeakReference, cette méthode renverra:

  • une référence vers l’objet si et seulement si la dernière vérification du garbage collector a révélé qu’il existe au moins une référence forte menant à lui.
  • sinon null.

En revanche, dans le cas d’une SoftReference, la méthode renverra:

  • null si et seulement si la mémoire allouée pour la machine virtuelle sature et qu’il n’existe plus de références fortes pointant vers l’objet faiblement référencé.
  • sinon la référence.

Cette différence nous indique dans quel cas utiliser l’une ou l’autre des références faibles:

  • On utilisera la classe SoftReference lorsque les données qui ne sont plus fortement référencées sont malgré tout susceptibles d’être utilisées plus tard, et que l’on souhaite donc les conserver autant que possible. Cette exigence serait tout à fait justifiée dans le cas d’un système mettant en cache des images: tant que la mémoire n’est pas saturée, il peut être intéressant de conserver des images qui ne sont plus utilisées car rien n’indique que l’utilisateur ne souhaitera pas les manipuler à nouveau. En faisant cela on économise le temps des accès sur le disque dur sans remettre en question la stabilité de l’application.
  • On utilisera la classe WeakReference dans les situations où les données qui ne sont plus fortement référencées peuvent être supprimées de la mémoire sans attendre.

Attention: dans la mesure où la référence de l’objet est susceptible d’être supprimée à tout moment, vous ne pouvez pas vous contenter de vérifier son existence à l’aide du test:

if(ref.get() != null) {
	// traitement
}

En effet, rien ne vous garantit que le garbage collector n’interviendra pas juste après votre vérification, pendant le traitement, en supprimant l’objet de la mémoire. Vous devez donc procéder comme suit afin de créer une référence forte empêchant toute intervention du GC par la suite:

Object o = ref.get();
if(o != null) {
	// traitement
}

Retrouver les références inutiles.
Il faut bien comprendre que ce sont les données référencées qui sont susceptible d’être supprimées, et en aucun cas les objets WeakReference et SoftReference utilisés. Ainsi dans l’exemple suivant:

String s1 = "s1";
String s2 = "s2";
 
ArrayList<WeakReference<String>> array = new ArrayList<WeakReference<String>>();
array.add(new WeakReference(s1));
array.add(new WeakReference(s2));
 
s2 = null;

à la fin de la manipulation, l’objet array contiendra toujours 2 éléments de type WeakReference. En revanche, array.get(1).get() renverra null et non un String contenant la valeur « s2″. Il nous reste donc à voir comment supprimer de la mémoire les WeakReference et SoftReference devenus inutiles.

Nettoyage des WeakReference/SoftReference inutiles.
Pour cela vous avez deux solutions:

  • la première consiste à parcourir régulièrement l’ensemble des références faibles dont vous disposez pour voir lesquelles d’entre elles peuvent être supprimées de la mémoire. Cette solution ne présente pas d’inconvénient si vous n’avez à parcourir qu’un faible nombre de données.
  • dans le cas contraire vous aurez l’utilité de la classe ReferenceQueue. Il s’agit en fait d’une liste que l’on placera dans le constructeur de nos références faibles. A chaque fois que le contenu d’une référence sera supprimée de la mémoire, une instance de cette même référence sera placé dans la liste. Vous pourrez donc suivre facilement le nombre de références faibles inutiles et procéder à leur nettoyage quand bon vous semblera.

Utilité des références faibles.
Les écouteurs d’évènements.
En Java, les écouteurs s’enregistrent auprès des diffuseurs d’évènements pour être averti lorsqu’une action a été déclenchée. Cela signifie que les diffuseurs possèdent tous une liste des écouteurs qu’ils doivent notifier; et c’est justement cette liste qui peut être à l’origine de fuites mémoire si le programmeur oublie de supprimer les écouteurs dont il n’a plus l’utilité. Mais, contrairement à ActionScript, Java n’offre pas pas la possibilité de demander, lors de l’enregistrement des écouteurs, l’utilisation de références faibles. Nous n’avons dans ce cas précis pas d’autre choix que d’utiliser les méthodes removeListener.
En revanche, rien ne vous empêche d’implémenter un système prenant en compte les références faibles lorsque vous programmez vos propres diffuseurs d’évènements.

Tables de hachage.
Il peut arriver que vous souhaitiez associer des données supplémentaires à des objets que vous ne pouvez pas étendre. L’utilisation d’une table de hachage se révèle alors intéressante, à la condition toutefois que vous pensiez à supprimer au fur et à mesure les clés que vous n’utilisez plus, ou bien que vous utilisiez une WeakHashMap qui fera le travail toute seule. En effet, la classe WeakHashMap encapsule elle même toutes les clés que vous lui passé dans des WeakReference, et pourra donc purger régulièrement les références dont le contenu est null.
Attention, la WeakHashMap ne peut en aucun cas servir de cache, car se sont les clés et non les données qui sont faiblement référencées. Ceci étant dit, rien en vous empêche de créer votre propre table de hachage dont les données seraient faiblement référencées.

Autre source: ici.