juin
2006
L’API Java est vraiment énorme. Elle offre un grand nombre d’outils et de possibilités qui, bien souvent, ne sont pas utilisées tout simplement parce qu’elles sont méconnues…
Cela faisait quelques temps que je voulais m’intéresser plus particulièrement aux diverses références de Java, et le récent article sur les références faibles sous .NET m’avait donné l’eau à la bouche… Et voilà qu’un article en anglais les décrit avec des exemples concrets : Understanding Weak References.
Je me suis donc jeter dedans et je vais essayer de vous présenter cela.
Références fortes (Strong references)
En tout bien tout honneur, je commencerais par les références fortes, qui ne sont ni plus ni moins que les références ordinaires des objets Java, que vous utilisez tous les jours :
StringBuffer buffer = new StringBuffer();
Le code ci-dessus crée un objet de type StringBuffer et stocke une référence forte dans la variable buffer.
Tant qu’un objet est accessible via une référence forte (on dit alors qu’il est « strongly reachable« ), il n’est pas éligible à la destruction par le Garbage Collector : ce dernier ne supprime pas les objets que vous utilisez, mais seulement ceux que vous n’utilisez plus, c’est à dire lorsque vous n’avez plus de références sur ces objets ou que vous les passez explicitement à null…
Toutefois, si ce mécanisme de référence permet de simplifier la gestion de la mémoire, en libérant le developpeur de gérer lui-même la libération des données, cela peut avoir certains désavantages dans certain cas.
Ainsi, avec Java 1.2 est apparut le package java.lang.ref, qui définit un ensemble de référence supplémentaire, qui permettent de définir des contraintes moins fortes qui s’avère utile dans certains cas.
Présentation du package java.lang.ref
Reference
Le package java.lang.ref défini trois nouveaux types de référence, qui héritent toutes de la classe abstraite Reference, qui définit l’ensemble des méthodes communes aux références.
Elle définit en particulier une méthode get() qui permet de récupérer une référence forte vers l’instance (si elle est disponible).
Ces références donnent moins de contraintes au GC, qui peut donc libérer les objets même s’il sont toujours référencés (sous certaines conditions). Ainsi, lorsque la référence n’est plus valide, sa méthode get() renvoi null.
ReferenceQueue
Il est possible d’être informé de la non-validité d’une référence en enregistrant cette dernière dans une ReferenceQueue. En effet dès que le GC modifie leurs états, les références s’ajoutent à la ReferenceQueue, qui propose trois méthodes pour les récupérer :
- La méthode poll() renvoit la première référence et la supprime de la queue, ou null si la queue ne comporte aucune référence.
- La méthode remove() supprime la première référence de la queue, en bloquant le thread courant si elle ne contient aucune référence.
- La méthode remove(long) permet exactement la même chose en utilisant un timeout.
Cette classe permet ainsi d’être tenu informée de l’état des références, et permet en particulier de les libérés de leurs références fortes (en effet, puisque ces références sont représentées par un objet, elles sont elles-même représentées par un référence forte).
Références douces (Soft references)
Les références douces (représentées par la classe SoftReference) permettent d’autoriser le GC à supprimer l’objet référencé si le besoin s’en fait sentir. Ainsi, si la charge mémoire est importante, il est garantit que toutes les références douces seront libérées AVANT qu’une OutOfMemoryException ne soit lancée. Cela permet de conserver en mémoire de gros objet afin d’éviter d’avoir à les recharger plus tard, tout en permettant au GC de les supprimer en cas de besoin…
Les références douces peuvent être libérer par le GC si aucune référence forte n’existe pour l’objet en question. Toutefois, les implémentations des JVM sont encouragées à ne pas supprimer les références douces récemment créées ou récemment utilisées…
Exemple d’utilisation : Gérer un cache d’objet
Bien entendu, la gestion de cache d’objet est le cas typique d’utilisation de SoftReference. En effet, imaginez une application qui utiliserait une (ou plusieurs) image(s) de taille conséquente à divers moments de son exécution. Si on conserve constamment une référence forte sur cette image, elle peut se retrouver avec des problèmes de mémoire alors que l’image n’est pas utilisée.
Avec une référence douce, l’image serait supprimé par le GC en cas de besoin, et il ne nous restera plus qu’à la recharger le cas échéant. Mais si le besoin de mémoire ne se fait pas sentir, l’image restera chargé et cela évitera ainsi de la recharger…
Par exemple, on pourrait utiliser le code suivant :
SoftReference<Image> softImage = new SoftReference<Image>(null);
public Image getImage() throws IOException {
Image image = this.softImage.get();
// Si la référence douce n'est pas valide
if (image==null) {
// On charge/recharge l'image
image = ImageIO.read(filename);
// Et on recrée une référence soft :
this.softImage = new SoftReference<Image>(image);
}
return image;
}
On peut bien sûr imaginer des systèmes de cache plus complexe en y combinant une Map ou une List qui se modifierait en conséquence (en utilisant une ReferenceQueue pour être tenu informé de l’invalidité des références)…
Selon un des commentaires de l’article anglais, la libération des références douces varie selon qu’on utilise la JVM client ou server :
- La JVM client tentera d’utiliser le moins de mémoire possible en supprimant les références douces avant d’augmenter la taille du heap (mémoire réservée).
- A l’inverse la JVM server tentera d’optimiser les performances en augmentant la taille du heap avant de supprimer les références douces.
Références faibles (Weak references)
Les références faibles (représentées par la classe WeakReference) permettent de stocker une référence, mais ne permettent pas d’assurer a elle seule que l’objet stocké sera conservé en mémoire. Les références faibles permettent ainsi de faire dépendre la durée de vie d’un objet par rapport à la validité d’un autre objet.
Les références faibles peuvent être libérer par le GC si aucune référence forte ni aucune références douces n’existe pour l’objet en question.
Exemple d’utilisation : Associer des valeurs à un objet
Lorsque l’on souhaite associer une valeur à un objet, la meilleur solution est bien sûr de créer un attribut spécifique. Toutefois, dans certain cas cela n’est pas possible, soit parce que l’on ne peut pas étendre l’objet, soit parce qu’on reçoit une implémentation spécifique.
Prenons le cas d’une Map qui permettrait d’associer un identifiant à un Process lancé via la classe Runtime, pendant toute la durée de vie de ce dernier. Si on utilise une simple HashMap, il nous faudrait enlever explicitement l’objet de la Map lorsqu’on souhaite qu’il soit libérer, sinon notre HashMap conserve une référence forte sur cet objet qui empêche toute libération par le GC.
L’API propose pour cela la classe WeakHashMap qui permet de faire cela simplement. Elle utilise en effet une WeakReference sur les clefs (et seulement les clefs) qui lui sont passées. Ainsi on peut avoir le code suivant :
Map<Process,String> map = new WeakHashMap<Process,String>();
Process process = Runtime.getRuntime().exec(...);
map.put(process, "process_1");
System.out.println( map.size() ); // affiche 1
//...
process = null;
System.out.println( map.size() ); // peut afficher 0
Lorsque la référence forte process n’existe plus, l’objet peut être libéré par le GC, et la WeakHashMap s’adapte en supprimant le couple clef/valeur qui n’est plus d’aucune utilité (on n’a pas besoin de conserver l’identifiant d’un objet que l’on ne va plus utiliser). Bien entendu, cela dépend du passage du GC, et donc la taille ne varie pas instantanément…
Références fantômes (Phantom references)
Pour finir, les références fantômes (représentées par la classe PhantomReference) sont un peu particulière dans le sens où leur méthode get() renverra toujours null. En effet l’objectif des références fantômes est d’être avertis de la suppression complète d’un objet par le GC. En effet, contrairement aux WeakReference et SoftReference qui sont « invalidées » dès que commence le processus de libération, les PhantomReferences ne sont ajoutées à la ReferenceQueue associée que lorsque l’objet référencé est complètement supprimé de la mémoire (cela permet d’éviter des « résurrections obscures » d’objet dans la méthode finalize()).
Les références fantômes peuvent être libérer par le GC si aucune référence forte, aucune références douces ni aucune référence faible n’existe pour l’objet en question.
Exemple d’utilisation : Libérer la mémoire
En effet, un grand nombre de personnes pensent qu’il est impossible de savoir si un objet a été libéré ou pas de la mémoire. Or c’est exactement ce que permet les références fantômes.
Lorsqu’on travaille avec de gros objets, cela permet par exemple de laisser le temps au GC de libérer les objets inutiles avant d’en charger d’autres.
La référence est ajouté en queue lorsque l’objet est complètement effacé de la mémoire…
// On crée un objet
Object o = new Object();
// On crée une queue et une référence fantôme pour cet objet
ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
PhantomReference<Object> ref = new PhantomReference<Object>(o, queue);
// On libère l'objet :
o = null;
// On force le GC :
System.gc();
// Et on attend que le GC libère vraiment l'objet
try {
queue.remove();
} catch (InterruptedException e) {
e.printStackTrace();
}
En résumé
- Les références douces (SoftReference) permettent de gérer des caches en autorisant la suppression si nécessaire.
- Les références faibles (WeakReference) permettent de référencer des objets sans pour autant empêcher leurs suppressions.
- Les références fantômes (PhantomReference) permettent de s’assurer de la destruction d’un objet.
Avec tout cela, vous avez toutes les cartes en main pour gérer efficacement la mémoire de vos applications Java, mais attention toutefois à ne pas utiliser ces références de manière abusive… après tout le Garbage Collector fait assez bien son travail…
7 Commentaires + Ajouter un commentaire
Tutoriels
Discussions
- Possibilité d'accéder au type générique en runtime
- Recuperation du nom des parametres
- L'apparition du mot-clé const est-il prévu dans une version à venir du JDK?
- Définition exacte de @Override
- Difference de performances Unix/Windows d'un programme?
- Classes, méthodes private
- [REFLEXION] Connaitre toutes les classes qui implémentent une interface
- jre 1.5, tomcat 6.0 et multi processeurs
- [ fuite ] memoire
Au sujet du commentaire fait par broumbroum en 2007 :
Il faut faire très attention aux « méthodes » utilisées par celui ci , car elle sont contextuelles vis à vis de la version de vm .
D’ailleurs il le disais lui même « ce sont mes observations par experience avec le debugger Java lors des tests avec Reference ».
L’implémentation du/des garbage collector à beaucoup évolué ces dernières années et la seule
manière de garantir l’efficacité des solutions proposées par l’article est d’utilisé les solutions de l’article , en lisant bien la fin
qui dis « mais attention toutefois à ne pas utiliser ces références de manière abusive… » ce qui était le cas de broubroum .
Merci au très bon auteur de cette article .
Très bon tuto. Merci beaucoup !
Pour les PhantomReference on peut aussi les sous-classer et leur associer un code qui sera déclenché en sortie : à la sortie de la queue on pourra faire:
// dans une boucle? <br />
Zombie zombie = zombieQueue.remove() ; // ou poll() <br />
zombie.postMortem() ; <br />
salut! j’ai une remarque par rapport au dernier paragraphe concernant la « liberation de la memoire ». Lorsque tu appelles la reference a liberer:
Object o = new Object(); <br />
// On crée une queue et une référence fantôme pour cet objet <br />
ReferenceQueue queue = new ReferenceQueue(); <br />
PhantomReference ref = new PhantomReference(o, queue); <br />
<br />
// On libère l'objet : <br />
o = null; <br />
/** On force le GC : c'est une façon de liberer la memoire, bien qu'elle devient tres lente lorsque ce code est repete, car il entre en conflit avec le Thread original du Systeme. de plus il collecte TOUS les objets */ <br />
//System.gc(); <br />
/** Et on attend que le GC libère vraiment l'objet : ici je prefere poll() car il est sur de trouver la derniere reference a liberer. */ <br />
try { <br />
//queue.remove(); <br />
((Reference)queue.poll()).clear(); <br />
/** liberation effective: pour cela j'utilise clear() que tu n'as pas cité dans l'article */ <br />
ref.clear(); <br />
} catch (InterruptedException e) { <br />
e.printStackTrace(); <br />
}
ce sont mes observations par experience avec le debugger Java lors des tests avec Reference. merci pour cet article!
@Jérôme >> Merci c’est corrigé
Merci pour cette excellent article. Plus complet que l’article Anglais.
Cela fait partie des fonctionnalités de Java à toujours avoir en tête et dont on n’entend pas souvent parler.
( une tite erreur, dans la partie sur les références douces, second paragraphe, tu as écris « Les références fantômes » au lieu de « douces ».
Jérôme
Hello,
C’est idée génial cet article!!
Fabszn