page : 1/19 COMMENT " REUSSIR " DES FUITES MEMOIRES EN JAVA? Date 05/12/08 Version Modification Author V1.0 Initial JL PASTUREL
page : 2/19 Table des matières 1 INTRODUCTION... 3 2 CLASSES DE TESTS... 3 2.1 Classe initiale avec fuite mémoire... 3 3 Génération fuite mémoire...4 3.1 Lancement application...4 3.2 Analyse avec Java VisualVM... 4 3.3 Conclusion... 6 4 CORRECTION DE LA FUITE MEMOIRE...6 4.1 Source classe corrigée...6 4.2 Lancement de l application...7 4.3 Analyse Java VisualVM... 8 4.4 Conclusion test NoMemoryLeak1... 9 5 OUI MAIS AVEC LES WEAKREFERENCE?... 9 5.1 Source classe...9 5.2 Lancement de l application...10 5.3 Analyse Java VisualVM... 10 5.4 Conclusion test MemoryLeak2...13 6 SOLUTION : java.util.weakhashmap... 13 6.1 La classe sans fuite mémoire NoMemoryLeak2...13 6.2 Lancement de l application...14 6.3 Analyse Java VisualVM... 14 6.4 Conclusion test NoMemoryLeak2... 15 7 Et si je dé-référence directement la Collection?...15 7.1 Source MemoryLeak3...16 7.2 Lancement de l application...17 7.3 Analyse Java VisualVM... 17 7.4 Conclusion test MemoryLeak3...18
page : 3/19 1 INTRODUCTION Le but de ce petit document est de montrer comment on peut générer des fuites mémoires en java en utilisant des Collections d objets. La JVM utilisée pour cette démonstration est la JVM HostSpot de SUN 1.6.0_10 sur Windows XP. Le suivi d occupation de la heap sera fait à l aide de l outil Java VisualVM fournie avec cette version. L outil intègre aussi un profiler 2 CLASSES DE TESTS Nous allons nous appuyer sur des classes simples, sans signification fonctionnelle réelle. 2.1 Classe initiale avec fuite mémoire La classe principale : MemoryLeak.java : import java.util.arraylist; public class MemoryLeak /** * @param args */ public ArrayList<Bourrage> listbourrage= new ArrayList<Bourrage>(); public void dosomething(string name) Bourrage bour=new Bourrage(name); listbourrage.add(bour); // Faire des choses avec bour bour=null; // Ne sert à rien, pour montre seulement que bour est bien de-reference ; public static void main(string[] args) // TODO Auto-generated method stub // On va creer la fuite MemoryLeak memleak=new MemoryLeak(); int nbboucles = Integer.parseInt(args[0]); for ( int i=0;i<nbboucles;i++ ) memleak.dosomething(args[1]); try Thread.sleep(10); catch (InterruptedException e) // TODO Auto-generated catch block
page : 4/19 e.printstacktrace(); La classe auxiliaire Bourrage.java : public class Bourrage // Un simble String public String name="monnom" ; public Bourrage(String _name) name=_name; 3 Génération fuite mémoire 3.1 Lancement application L application est lancée avec les paramètres suivants : C:\eclipse\workspace\\bin>java -Xms2M -Xmx2M MemoryLeak 10000000 tttyttttttttytttthhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhttttttttttttttt 3.2 Analyse avec Java VisualVM La console Java VisualVM donne :
page : 5/19 Le diagramme en haut à gauche montre l effet de la fuite mémoire. La mémoire résiduelle après Garbage a tendance à monter au fur et à mesure de la vie de l application. On notera aussi que malgré le paramètre -Xmx à 2Mo la heap de l JVM continue à grandir au delà des 2Mo.
page : 6/19 1 heure après : 3.3 Conclusion Lors d un dé-référencement d objet contenu dans une Collection, il faut aussi le retirer de la Collection sinon fuite mémoire garantie. L objet ArrayList garde dans sa collection une seconde Strong Reference de l objet Bourrage. Un objet n est garbageable que si toutes ses Strong ( Hard) References sont à null. 4 CORRECTION DE LA FUITE MEMOIRE 4.1 Source classe corrigée NoMemoryLeak1.java : import java.util.arraylist;
page : 7/19 public class NoMemoryLeak1 /** * @param args */ public ArrayList<Bourrage> listbourrage= new ArrayList<Bourrage>(); public void dosomething(string name) Bourrage bour=new Bourrage(name); listbourrage.add(bour); // Faire des choses avec bour // ici ce coup-ci on retire bour de la Collection : listbourrage.remove(bour); bour=null; // Ne sert à rien, pour montre seulement que bour est bien de-reference ; public static void main(string[] args) // TODO Auto-generated method stub // On va creer la fuite NoMemoryLeak1 nomemleak=new NoMemoryLeak1(); int nbboucles = Integer.parseInt(args[0]); for ( int i=0;i<nbboucles;i++ ) nomemleak.dosomething(args[1]); try Thread.sleep(10); catch (InterruptedException e) // TODO Auto-generated catch block e.printstacktrace(); Noter en gras la suppression de l objet de la collection avant son dé-référencement. 4.2 Lancement de l application C:\eclipse\workspace\\bin>java -Xms2M -Xmx2M NoMemoryLeak1 10000000 tt tyttttttttytttthhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhttttttttttttttt
page : 8/19 4.3 Analyse Java VisualVM Sur ce test, on voit sur le diagramme en haut à gauche qu il n y a plus de fuite mémoire car la taille de la mémoire résiduelle après provocation d un Full GC n augmente plus. Le nombre d objets Bourrage en vie est aussi réduit par rapport au tir précédent.
page : 9/19 4.4 Conclusion test NoMemoryLeak1 La suppression de l objet Bourrage de la Collection a permis de supprimer la fuite mémoire. 5 OUI MAIS AVEC LES WEAKREFERENCE? Pour cet test de la fuite mémoire nous allons utiliser le mécanisme des références faibles proposées par Java. Ici nous utiliserons une WeakReference. Pour plus d information en français : http://gfx.developpez.com/tutoriel/java/reference-memoire/ http://blog.developpez.com/adiguba?title=comprendre_les_references_en_java 5.1 Source classe MemoryLeak2.java : import java.lang.ref.weakreference; import java.util.arraylist; public class MemoryLeak2 /** * @param args */ // La Collection ArrayList ne contient que des WeakReference public ArrayList<WeakReference<Bourrage>> listbourrage= new ArrayList<WeakReference<Bourrage>>(); public void dosomething(string name) Bourrage bour=new Bourrage(name); // Utilisation Avancée // Creation d'une WeakReference sur l'objet bour WeakReference<Bourrage> wrbour=new WeakReference<Bourrage>(bour); // On met la weakreference dans la collection à la place de la Strong Reference listbourrage.add(wrbour); // Faire des choses avec bour bour=null; // Ne sert à rien, pour montre seulement que bour est bien de-reference ; public static void main(string[] args)
page : 10/19 // TODO Auto-generated method stub // On va creer la fuite MemoryLeak2 memleak=new MemoryLeak2(); int nbboucles = Integer.parseInt(args[0]); for ( int i=0;i<nbboucles;i++ ) memleak.dosomething(args[1]); try Thread.sleep(10); catch (InterruptedException e) // TODO Auto-generated catch block e.printstacktrace(); Noter en gras l'utilisation de l ArrayList de WeakReference sur l objet Bourrage 5.2 Lancement de l application C:\eclipse\workspace\\bin>java -Xms2M -Xmx2M MemoryLeak2 10000000 tt tyttttttttytttthhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhttttttttttttttt 5.3 Analyse Java VisualVM Ben on a toujours des fuites mémoires malgré l utilisation de WeakRreference.
page : 11/19
page : 12/19
page : 13/19 Le nombre de WeakReference augmente comme lors du premier test avec l objet Bourrage.. 5.4 Conclusion test MemoryLeak2 Le remplacement de la référence Strong (Hard) par une référence plus faible sur l objet dans la Collection ArrayList n a pas permis d éliminer la fuite mémoire. L objet WeakReference a lui même une Strong Reference, on retombe sur le même problème que lors du test initial. Je laisse au lecteur le soin de corriger le code en supprimant la Weak référence de la structure ArrayList comme vue lors de la première Collection. Ceci est lié au fait que l Objet ArrayList ne gère pas le fait que son contenu est de type Reference faible. Pour cela, il faut utiliser une autre type de Collection : java.util.weakhashmap 6 SOLUTION : java.util.weakhashmap La classe java.util.weakhashmap contient des couples clé/valeur en utilisant des références faibles sur la clé seulement, donc si la clé n'est plus référencée par une référence forte ailleurs dans le programme, le couple est automatiquement supprimé de la collection. 6.1 La classe sans fuite mémoire NoMemoryLeak2 Donc en gérant les référence de Bourrage dans l objet java.util.weakhashmap. NoMemoryLeak2.java : import java.util.weakhashmap; public class NoMemoryLeak2 // pour servir de clé à WeakHashMap static Long index=0l; /** * @param args */ // public ArrayList<Bourrage> listbourrage= new ArrayList<Bourrage>(); // Remplacement par l'objet java.util.weakhashmap public WeakHashMap whmbourrage= new WeakHashMap(); public void dosomething(string name) Bourrage bour=new Bourrage(name); whmbourrage.put(index,bour);
page : 14/19 index++; // Faire des choses avec bour bour=null; // Ne sert à rien, pour montre seulement que bour est bien de-reference ; public static void main(string[] args) NoMemoryLeak2 nomemleak=new NoMemoryLeak2(); int nbboucles = Integer.parseInt(args[0]); for ( int i=0;i<nbboucles;i++ ) nomemleak.dosomething(args[1]); try Thread.sleep(10); catch (InterruptedException e) // TODO Auto-generated catch block e.printstacktrace(); 6.2 Lancement de l application C:\eclipse\workspace\\bin>java -Xms2M -Xmx2M NoMemoryLeak2 10000000 tt tyttttttttytttthhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhttttttttttttttt
page : 15/19 6.3 Analyse Java VisualVM Le diagramme du haut à gauche montre qu il n y a pas de fuite mémoire
page : 16/19 L historique des objets Bourrage montre qu il sont bien garbagés ( trait rouge) et ne contribue pas à la fuite mémoire. 6.4 Conclusion test NoMemoryLeak2 L utilisation de l objet WeakHashMap permet d éviter les fuites mémoires sans avoir à supprimer explicitement l objet dans la Collection. 7 Et si je dé-référence directement la Collection? Il s agit de vérifier que lorsque je positionne à null la référence de l objet ArrayList, les Strong (Hard) Références contenues sont éligibles au garbage. 7.1 Source NoMemoryLeak4 NoMemoryLeak4.java :
page : 17/19 import java.util.arraylist; public class MemoryLeak4 /** * @param args */ public ArrayList<Bourrage> listbourrage= new ArrayList<Bourrage>(); public void dosomething(string name) Bourrage bour=new Bourrage(name); listbourrage.add(bour); // Faire des choses avec bour bour=null; // Ne sert à rien, pour montre seulement que bour est bien de-reference ; public static void main(string[] args) // TODO Auto-generated method stub // On va creer la fuite NoMemoryLeak4 nomemleak=new NoMemoryLeak4(); int nbboucles = Integer.parseInt(args[0]); for ( int i=0;i<nbboucles;i++ ) // Toutes les 1000 boucles on supprime et recrée la Collection ArrayList if(i%1000==0) nomemleak.listbourrage=null; nomemleak.listbourrage= new ArrayList<Bourrage>(); nomemleak.dosomething(args[1]); try Thread.sleep(10); catch (InterruptedException e) // TODO Auto-generated catch block e.printstacktrace(); Remarque : Il est évident que fonctionnellement et d un point de vue " best practice " ce code n a pas lieu d être dans une aucune application digne de ce nom.. Noter en gras, le dé-référencement toutes les 1000 lignes de l Objet ArrayList. 7.2 Lancement de l application C:\eclipse\workspace\\bin>java -Xms2M -Xmx2M NoMemoryLeak4 10000000 tt tyttttttttytttthhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhttttttttttttttt
page : 18/19 7.3 Analyse Java VisualVM Le diagramme du haut à gauche montre que lors des Full Garbage Collector la taille de la mémoire résiduelle revient au même niveau. Et en suivant l historique de la classe Bourrage, on constate bien que les instances sont bien garbagées.
page : 19/19 Les objets Bourrage sont bien garbagés. Remarque : Le nom de la classe n est donc pas correct puisqu il n y pas de fuite mémoire. On la renommera NoMemoryLeak4.java 7.4 Conclusion test NoMemoryLeak4 Le dé-référencement d un objet Collection ( Conteneur) dé-référence les objets qu il contient.