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.
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.
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.
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 :
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).
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...
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 :
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.
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...
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.
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(); }
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...
http://blog.developpez.com/htsrv/trackback.php?tb_id=2107
// 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 queue = new ReferenceQueue();
PhantomReference ref = new PhantomReference(o, queue);
// On libère l'objet :
o = null;
/** 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 */
//System.gc();
/** 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. */
try {
//queue.remove();
((Reference)queue.poll()).clear();
/** liberation effective: pour cela j'utilise clear() que tu n'as pas cité dans l'article */
ref.clear();
} catch (InterruptedException e) {
e.printStackTrace();
}
// dans une boucle?
Zombie zombie = zombieQueue.remove() ; // ou poll()
zombie.postMortem() ;
Vous devez être identifié pour poster un commentaire.

Ce blog est mis à disposition sous un contrat Creative Commons BY-NC-SA.
System.gc() a encore frappé : jre 1.5, tomcat 6.0 et multi processeurs
Tutoriels Java SE
Tutoriels Java EE
Sélection du blog
Java SE/EE et Web en général
| Lun | Mar | Mer | Jeu | Ven | Sam | Dim |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
Copyright © 2000-2012 - www.developpez.com