Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage Java Discussion :

[ fuite ] memoire


Sujet :

Langage Java

  1. #1
    Membre régulier
    [ fuite ] memoire
    Bonjour,

    Auriez vous un exemple simple de code java pour la fuite de memoire?




    Merci
    hocine

  2. #2
    Membre chevronné
    Exemple tiré de la FAQ
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    String resultat = ""; // création d'un chaine vide
    for( int i=0; i<10; i++) {
            resultat = resultat + i;
    } 
    System.out.println(resultat); // "0123456789"

  3. #3
    Membre actif
    Voici un article qui illustre la problématique des fuites mémoires en Java (et qui propose une solution en utilisant les WeakReference Java).
    Par contre si on veut rester précis, le terme "fuite mémoire" (memory leak) est plutôt un abus de langage en Java (par rapport aux fuites mémoire de langages non managés comme C ou C++ par exemple) : voir début de cet article.

  4. #4
    Membre expérimenté
    Citation Envoyé par morph41 Voir le message
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    String resultat = ""; // création d'un chaine vide
    for( int i=0; i<10; i++) {
            resultat = resultat + i;
    } 
    System.out.println(resultat); // "0123456789"
    Ce code ne provoque pas de fuite mémoire.

  5. #5
    Membre chevronné
    Citation Envoyé par herve91 Voir le message
    Ce code ne provoque pas de fuite mémoire.
    A chaque itération on créé une nouvelle String inutile en mémoire, suivant la taille de la boucle la perte peut être importante. On ne peut pas considérer ça comme une fuite mémoire?

  6. #6
    Membre expérimenté
    Non, car la mémoire est toutefois récupérée par le GC.
    Une fuite mémoire, c'est quand un objet n'est plus accessible "logiquement" de l'application, mais est toujours référencé par un autre objet.

    Exemple :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class Stack {
     
        private Object[] list;
        int sp;
     
        public Stack(int size) {
           list = new Object[size];
        }
     
        public push(Object o) {
           list[sp++] = o;
        }
     
        public Object pop() {
           return list[--sp]; // Ici on garde toujours la référence de l'objet 
        }
    }


    Il faut faire :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
        public Object pop() {
           Object o = list[--sp];
           list[sp] = null; // !!!
           return o;
        }

  7. #7
    Expert éminent sénior
    Salut,


    Citation Envoyé par morph41 Voir le message
    A chaque itération on créé une nouvelle String inutile en mémoire, suivant la taille de la boucle la perte peut être importante. On ne peut pas considérer ça comme une fuite mémoire?
    Non pas vraiment : c'est plutôt une consommation abusive (et surtout inutile) d'objets temporaires, et donc une grosse consommation de mémoire. C'est bien d'éviter cela mais ce n'est pas une fuite

    A la fin de la boucle les objets temporaires peuvent légitimement être nettoyé par le GC.



    Comme l'a signalé mOuLi les fuites de mémoire n'existe pas vraiment en Java pure, puisque dans les autres langages il s'agit généralement de pointeur dont on perd la valeur et qu'on ne peut donc plus libérer.
    En Java lorsqu'on perd la valeur d'une référence, le GC s'occupera de libérer l'objet...




    En fait en Java les fuites de mémoire correspondent plutôt à l'inverse, c'est à dire lorsqu'on conserve des références à des objets que l'on ne va plus utiliser, ou que l'on conserve un trop grand nombre d'objet...







    Ainsi la majorité des "fuites" vient du fait que l'on n'utilise pas correctement le bon scope pour une variable, le plus souvent par facilité ou parce qu'on croit par erreur économiser la mémoire en économisant les variable, par exemple :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    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
    class Tools {
    	private static StringBuilder buf = null;
     
    	/**
             * Crée une chaine de caractère de n charactère c
             * @param n Le nombre de caractère de la chaine
             * @param c Le caractère utilisé pour initialisé la chaine
             * @return
             */
    	public static String makeString(int n, char c) {
    		buf = new StringBuilder(n);
    		for (int i=0; i<n; i++) {
    			buf.append(c);
    		}
    		return buf.toString();
    	}
     
    	/**
             * Crée une chaine composé de plusieurs lignes
             * @param strings Chaque paramètre correspond à une ligne
             * @return
             */
    	public static String join(String...strings) {
    		buf = new StringBuilder();
    		for (String s : strings) {
    			buf.append(s).append(System.getProperty("line.separator"));
    		}
    		return buf.toString();
    	}
    }

    Ici la variable buf est mise en static car elle est "utilisé" dans deux méthodes. Or c'est une erreur : oci buf est bel et bien une variable temporaire et devrait le rester !!!
    (note : on retrouve également ce type d'erreur avec des variable d'instance...)


    Pourquoi ? C'est simple prenons le code suivant :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    	public void maMethod() {
    		String s = Tools.makeString(500000, 'a');
     
    		// ...
    	}

    C'est tout bête, on crée une chaine de 500000 'a', qui sera proprement libérable à la fin de la méthode... toutefois le champs static et privée Tools.buf existera toujours pour rien, et cela jusqu'à ce qu'on utilise une autre des méthodes static de Tools

    Le fait de mettre buf à null à la fin de chaque méthode n'est pas non plus une solution... c'est plutôt casse-gueule (le risque d'oubli est important).


    La solution est bien sûr d'utiliser une variable locale, ce qui permet de limiter fortement le scope de l'objet :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    	/**
             * Crée une chaine de caractère de n charactère c
             * @param n Le nombre de caractère de la chaine
             * @param c Le caractère utilisé pour initialisé la chaine
             * @return
             */
    	public static String makeString(int n, char c) {
    		final StringBuilder buf = new StringBuilder(n);
    		for (int i=0; i<n; i++) {
    			buf.append(c);
    		}
    		return buf.toString();
    	}

    A la fin de la méthode buf sera libérable par le GC.
    En plus cela permet de déclarer la variable final et donc de bénéficier potentiellement de quelques petites optimisations du couple compilateur/JVM




    Bref je dirais que la règle numéro 1 est de bien vérifier le scope des variables, et de le réduire au strict minimum ! Cela peut sembler anodin mais sur une grosse application cela peut avoir une grosse importance !







    Ensuite une grosse source d'erreur vient de la gestion des ressources systèmes ou externes (fichiers, sockets, connexion JDBC, ...) qui ne peuvent pas être gérée par le GC.

    Exemple : la lecture d'un fichier via une URL :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    	public static String read(URL url) throws IOException {
    		Reader reader = new InputStreamReader(url.openStream());
    		StringBuilder buf = new StringBuilder();
    		char[] cbuf = new char[8192];
    		int len;
     
    		while ( (len = reader.read(cbuf)) >= 0) {
    			buf.append(cbuf, 0, len);
    		}
    		return buf.toString();
    	}

    Ici on ne ferme jamais la socket ouverte par url.openStream(), et elle reste donc ouverte même après la fin de la méthode (je parle de la socket native et non pas de l'objet Java).

    Note : en vérité la socket pourrait être fermée indirectement car il y a généralement un "garde-fou" basé sur la finalisation, mais il ne faut pas se baser dessus : ces objets représentent peu d'occupation mémoire pour le GC, mais peuvent engendrer de grosses ressources systèmes invisible par le GC, et du coup la finalisation pourrait intervenir trop tard...


    Pour régler le problème il faut bien sûr libérer les ressources via la méthode close(), mais il faut utiliser un bloc try/finally sinon on peut passer au travers (lire : Comment libérer proprement les ressources (ou comment utiliser proprement les bloc try/finally) ?).

    Ce qui donnerait :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    	public static String read(URL url) throws IOException {
    		Reader reader = new InputStreamReader(url.openStream());
    		try {
    			StringBuilder buf = new StringBuilder();
    			char[] cbuf = new char[8192];
    			int len;
     
    			while ( (len = reader.read(cbuf)) >= 0) {
    				buf.append(cbuf, 0, len);
    			}
    			return buf.toString();
    		} finally {
    			// Quoi qu'il arrive (return ou exception)
    			// On ferme le flux :
    			reader.close();
    		}
    	}










    Enfin en ce qui concerne les WeakReferences et autres, ce n'est pas vraiment la solution absolu aux fuites de mémoire, mais correspondent plutôt à des solutions bien spéciques...

    • Les WeakReferences permet de conserver une référence sur un objets sans empêcher sa libération lorsque l'objet n'est plus référencé ailleurs la WeakReference devient invalide et l'objet peut être libéré...

      On peut prendre comme exemple les WeakHashMap qui permettent de conserver des valeurs tant que la clef est valide...
    • Les SoftReference sont quand à elles destiné à conserver des objets en mémoire afin d'éviter de les recréer, sans pour autant que cela ne cause d'OutOfMemory.

      En fait lorsqu'on a une SoftReference le GC ne détruira l'objet qu'en dernier recours, et il le gardera tant que possible. C'est parfait pour faire un cache de données : on conserve les données même si elle ne sont plus utilisé, mais si l'espace mémoire manque le GC détruira ces données...



    Plus d'info sur le blog : Comprendre les références en Java


    a++
    Cette signature n'a pas pu être affichée car elle comporte des erreurs.

  8. #8
    Membre expérimenté
    Je ne sais pas sous quel item mais ça mériterait d'être conservé dans la FAQ ça ...

    Cette signature n'a pas pu être affichée car elle comporte des erreurs.

  9. #9
    Membre averti
    Salut à tous

    Citation Envoyé par adiGuba Voir le message
    En plus cela permet de déclarer la variable final et donc de bénéficier potentiellement de quelques petites optimisations du couple compilateur/JVM
    d'abord, chapeau adiGuba : +1 in

    ma question : en quoi représente les final un gain en optimisation ?

  10. #10
    Expert éminent sénior
    Citation Envoyé par Chatbour Voir le message
    ma question : en quoi représente les final un gain en optimisation ?
    La variable ne sera jamais modifié : le compilateur et/ou la JVM peuvent donc effectuer des optimisations plus "agressive".

    Pour un attribut d'instance la JVM évite de vérifier la valeur de la référence à chaque fois, et les types primitifs pourraient être remplacés par des constantes...

    Sans oublier que cela apporte une information très important dans le code : on sait que la valeur/référence ne sera jamais modifié !

    A utiliser autant que possible

    a++
    Cette signature n'a pas pu être affichée car elle comporte des erreurs.

  11. #11
    Membre averti
    Merci bien

  12. #12
    Membre régulier
    très bon tuto! merci pour ce cours

  13. #13
    Membre du Club
    adiGuba : super interessant ce post. J'ai dû chercher pour tomber dessus, ce serait bien de l'avoir dans une page "les bonnes pratiques java". Je vais chercher, elle existe peut être déjà cette page.

  14. #14
    Membre du Club
    Merci pour ce post très intéressant.
    Je rencontre justement une fuite de mémoire très gênante, qui bloque mon application au bout d'une vingtaine d'itérations.
    J'ai compris à peu près les principes énoncés ci-dessus pour éviter les fuites, mais je voudrais bien localiser la partie de mon code responsable.
    J'ai vu sur des posts anciens ou des FAQ qu'il existait des outils à installer qui permettaient d'explorer la structure de la mémoire pour identifier les objets inutilement conservés.
    Quel est l'état le récent de la science en la matière ? les derniers outils les plus simples à prendre en main ?
    Les dernières versions d'Eclipse intègrent-elles de tels outils ?

###raw>template_hook.ano_emploi###