IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
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 :

Cas d'étude : Java et fuite mémoire


Sujet :

Langage Java

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre averti
    Homme Profil pro
    Inscrit en
    Janvier 2012
    Messages
    16
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Janvier 2012
    Messages : 16
    Par défaut Cas d'étude : Java et fuite mémoire
    Bonjour,

    Je suis en train de coder un cas d'étude pour provoquer une fuite mémoire, notamment en employant des variables statiques.
    Pour mettre en évidence la cause de la fuite mémoire, je mets en confrontation 2 programmes (2 main() distincts), l'un qui alloue (indirectement) un gros objet statique, l'autre qui alloue (indirectement) un gros objet NON statique.
    Je m'attends à ce que le premier plante.
    Je m'attends à ce que le deuxième marche.
    Or ce n'est pas le cas, ils plantent tous les deux pour problème de mémoire, je ne comprends pas pourquoi, pouvez-vous m'enseignez la théorie des dessous de la JVM .

    Merci.

    Le code :

    Fichier FuiteMemoireAvecStaticMain.java
    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
     
    package fr.memoryleak;
     
    import java.lang.management.ManagementFactory;
    import java.lang.management.RuntimeMXBean;
    import java.util.List;
     
    /**
     * Augmente la valeur d'un compteur (version non static)
     */
    public class FuiteMemoireAvecStaticMain {
     
      /**
       * @param args
       */
      public static void main(String[] args) throws Exception {
        System.out.println("begin");
        Compteur compteur = new Compteur();
        RuntimeMXBean RuntimemxBean = ManagementFactory.getRuntimeMXBean();
        List<String> arguments = RuntimemxBean.getInputArguments();
        System.out.println("JVM running arguments : " + arguments);
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
          compteur.incrementerCompteurAvecStatic();
          System.gc();
        }
        System.out.println("end");
     
      }
     
    }
    Fichier FuiteMemoireSansStaticMain .java
    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
    package fr.memoryleak;
     
    import java.lang.management.ManagementFactory;
    import java.lang.management.RuntimeMXBean;
    import java.util.List;
     
    /**
     * Augmente la valeur d'un compteur (version non static)
     * 
     */
    public class FuiteMemoireSansStaticMain {
     
      public static void main(String[] args) throws Exception {
        System.out.println("begin");
        Compteur compteur = new Compteur();
        RuntimeMXBean RuntimemxBean = ManagementFactory.getRuntimeMXBean();
        List<String> arguments = RuntimemxBean.getInputArguments();
        System.out.println("JVM running arguments : " + arguments);
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
          compteur.incrementerCompteurSansStatic();
          System.gc();
        }
        System.out.println("end");
      }
     
    }
    Fichier Compteur .java
    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
    31
    package fr.memoryleak;
     
    /**
     * Gestion d'un compteur
     * 
     */
    public class Compteur {
     
      private int valeurAvecStatic = 0;
     
      private int valeurSansStatic = 0;
     
      /**
       * Gestion d'un compteur (version static)
       */
      public void incrementerCompteurAvecStatic() {
        FuiteMemoire fuiteMemoire = new FuiteMemoire();
        fuiteMemoire.creerFuiteMemoireAvecStatic();
        System.out.println("Valeur compteur : " + valeurAvecStatic++);
      }
     
      /**
       * Gestion d'un compteur (version non static)
       */
      public void incrementerCompteurSansStatic() {
        FuiteMemoire fuiteMemoire = new FuiteMemoire();
        fuiteMemoire.creerFuiteMemoireSansStatic();
        System.out.println("Valeur compteur : " + valeurSansStatic++);
      }
     
    }
    Fichier FuiteMemoire .java
    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
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    package fr.memoryleak;
     
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
     
    /**
     * Classe responsable d'une fuite mémoire
     * 
     */
    public class FuiteMemoire {
     
      private static final int MAP_INIT_CAPACITY = 10;
     
      private static final int NON_STATIC_MAX_ELEMENT = (int) 4e4;
     
      private static final int STATIC_MAX_ELEMENT = (int) 1e4;
     
      private static List<Map<String, String>> staticMapList = new ArrayList<Map<String, String>>();
     
      static {
        for (int i = 0; i < STATIC_MAX_ELEMENT; i++) {
          staticMapList.add(new HashMap<String, String>(MAP_INIT_CAPACITY));
        }
      }
     
      private final List<Map<String, String>> nonStaticMapList = new ArrayList<Map<String, String>>();
     
      /**
       * Creer une fuite memoire (version static)
       */
      public void creerFuiteMemoireAvecStatic() {
        for (int i = 0; i < STATIC_MAX_ELEMENT; i++) {
          staticMapList.add(new HashMap<String, String>(MAP_INIT_CAPACITY));
        }
      }
     
      /**
       * Creer une fuite memoire (version non static)
       */
      public void creerFuiteMemoireSansStatic() {
        for (int i = 0; i < NON_STATIC_MAX_ELEMENT; i++) {
          nonStaticMapList.add(new HashMap<String, String>(MAP_INIT_CAPACITY));
        }
      }
    }
    Sortie premier programme FuiteMemoireAvecStaticMain :
    begin
    JVM running arguments : [-Xmx4m, -Dfile.encoding=Cp1252]
    Valeur compteur : 0
    Valeur compteur : 1
    Valeur compteur : 2
    Valeur compteur : 3

    Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
    Sortie deuxième programme FuiteMemoireSansStaticMain:
    begin
    JVM running arguments : [-Xmx4m, -Dfile.encoding=Cp1252]
    Valeur compteur : 0
    Valeur compteur : 1
    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at fr.memoryleak.FuiteMemoire.creerFuiteMemoireSansStatic(FuiteMemoire.java:44)
    at fr.memoryleak.Compteur.incrementerCompteurSansStatic(Compteur.java:27)
    at fr.memoryleak.FuiteMemoireSansStaticMain.main(FuiteMemoireSansStaticMain.java:20)

  2. #2
    Expert éminent
    Avatar de tchize_
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    25 482
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : Belgique

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 25 482
    Par défaut
    tu travaille avec un Xmx de 4M, c'est très peu. N'oublie pas que tu as pas mal de structures internent à la JVM qui consomment de la mémoire, en plus de ce que tu déclare. Cette consommation est +- fixe et j'ai jamais vu une jvm arriver à démarrer avec un Xmx de 4M. Félicitation
    De plus, en non statique, tu alloue des éléments 4 fois plus grand dans ton code. Donc si je calcule à la grosse louche, tu crée en non statique une liste de 4000 Hashmap. Hors ca ne métonnerais pas qu'un hashmap occupe 0.5k ou un truc du genre. Ce qui nous donne une allocation de quoi, 2M? Ca laisse 2M pour le reste de la JVM. Ma main à couper qu'entre l'invocation 0 et 1 le GC a eu besoin d'initialiser une structure interne qui a fait passer ta mémoire nécessaire de 3.9M à 4.1M -> Et boom!


    Monte le Xmx à 16M et tu va tout de suite voir les effets de ton code. Aussi, soit honnête dans ton test, et alloue la même quantité à chaque opération de chaque coté. J'ai toujour trouvé qu'utiliser un new int[1000000] //allocation d'un bloc de 4M, était plus facile à gérer pour ce genre de démo

  3. #3
    Membre averti
    Homme Profil pro
    Inscrit en
    Janvier 2012
    Messages
    16
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Janvier 2012
    Messages : 16
    Par défaut
    Je sais qu'entre la valeur 0 et 1, le GC a tourné une fois a a pu libéré la mémoire en supprimant les objets non référencés. Sa structure a pu être initialisée.
    Mais pourquoi dans la version NON statique, le compteur ne va-t-il pas au delà de 1 ? Les objets crées lors des appels précédents à la méthode
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    incrementerCompteurSansStatic
    auraient du être garbage collecté. A partir du moment où j'arrive à afficher au moins une valeur du compteur, je peux boucler sans excédant mémoire.

  4. #4
    Modérateur

    Profil pro
    Inscrit en
    Septembre 2004
    Messages
    12 585
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2004
    Messages : 12 585
    Par défaut
    Je ne sais pas vraiment, mais en ce qui me concerne je ne m'attendrais pas à ce que hotspot arrive à bien gérer sa mémoire si on ne lui donne que 4 mégas. En théorie, peut-être qu'il pourrait, mais il est censé avoir un algorithme complexe adapté à des situations qu'on rencontre dans le monde réel, pas avec 4Mo de mémoire.
    N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java

  5. #5
    Expert éminent
    Avatar de tchize_
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    25 482
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : Belgique

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 25 482
    Par défaut
    Citation Envoyé par Bobak42 Voir le message
    Je sais qu'entre la valeur 0 et 1, le GC a tourné une fois a a pu libéré la mémoire en supprimant les objets non référencés. Sa structure a pu être initialisée.
    Mais pourquoi dans la version NON statique, le compteur ne va-t-il pas au delà de 1 ? Les objets crées lors des appels précédents à la méthode
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    incrementerCompteurSansStatic
    auraient du être garbage collecté. A partir du moment où j'arrive à afficher au moins une valeur du compteur, je peux boucler sans excédant mémoire.
    Oui tes données ont été libérées entre 0 et 1. Mais le GC ou tout autre système tournant en // sur la jvm a très bien pu quémander quelques un des ces octets libérés. Résultat, entre l'appel 0 et 1, le besoin total de mémoire est probablement passé de 3.9M à 4.1M et couic.

  6. #6
    Membre averti
    Homme Profil pro
    Inscrit en
    Janvier 2012
    Messages
    16
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Janvier 2012
    Messages : 16
    Par défaut
    Bon après toutes vos remarques, j'ai retouché le code.
    Le résultat est le même ...

    Fichier Compteur.java :

    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
    package fr.memoryleak;
     
    /**
     * Gestion d'un compteur
     * 
     */
    public class Compteur {
     
      private int valeurAvecStatic = 0;
     
      private int valeurSansStatic = 0;
     
      /**
       * Gestion d'un compteur (version static)
       */
      public void incrementerCompteurAvecStatic() {
        FuiteMemoireAvecStatic fuiteMemoireAvecStatic = new FuiteMemoireAvecStatic();
        System.out.println("Valeur compteur : " + valeurAvecStatic++);
      }
     
      /**
       * Gestion d'un compteur (version non static)
       */
      public void incrementerCompteurSansStatic() {
        FuiteMemoireSansStatic fuiteMemoireSansStatic = new FuiteMemoireSansStatic();
        System.out.println("Valeur compteur : " + valeurSansStatic++);
      }
     
    }

    Fichier FuiteMemoireAvecStatic.java :

    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
    package fr.memoryleak;
     
    /**
     * Classe responsable d'une fuite mémoire (avec membre statique)
     * 
     */
    public class FuiteMemoireAvecStatic {
     
      private static final int TABLEAU_TAILLE_MAX = 1371690;
     
      private static Integer[] tableauEntier;
     
      public FuiteMemoireAvecStatic() {
        tableauEntier = new Integer[TABLEAU_TAILLE_MAX];
        System.out.println("Allocation d'un gros tableau statique : "
            + tableauEntier);
      }
    }

    Fichier FuiteMemoireSansStatic.java :

    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
    package fr.memoryleak;
     
    /**
     * Classe responsable d'une fuite mémoire (sans membre statique)
     * 
     */
    public class FuiteMemoireSansStatic {
     
      private final int TABLEAU_TAILLE_MAX = 2743460;
     
      private final Integer[] tableauEntier;
     
      public FuiteMemoireSansStatic() {
        tableauEntier = new Integer[TABLEAU_TAILLE_MAX];
        System.out.println("Allocation d'un gros tableau non statique : "
            + tableauEntier);
      }
    }
    Sortie premier programme FuiteMemoireAvecStaticMain :
    begin
    JVM running arguments : [-Xmx16m, -Dfile.encoding=Cp1252]
    Allocation d'un gros tableau statique : [Ljava.lang.Integer;@6e6e056e
    Valeur compteur : 0
    Allocation d'un gros tableau statique : [Ljava.lang.Integer;@659c2931
    Valeur compteur : 1
    ...
    Allocation d'un gros tableau statique : [Ljava.lang.Integer;@9732fe5
    Valeur compteur : 677
    Allocation d'un gros tableau statique : [Ljava.lang.Integer;@3663270d
    Valeur compteur : 678
    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at fr.memoryleak.FuiteMemoireAvecStatic.<init>(FuiteMemoireAvecStatic.java:14)
    at fr.memoryleak.Compteur.incrementerCompteurAvecStatic(Compteur.java:17)
    at fr.memoryleak.FuiteMemoireAvecStaticMain.main(FuiteMemoireAvecStaticMain.java:22)
    Sortie deuxième programme FuiteMemoireSansStaticMain:
    begin
    JVM running arguments : [-Xmx16m, -Dfile.encoding=Cp1252]
    Allocation d'un gros tableau non statique : [Ljava.lang.Integer;@6601879b
    Valeur compteur : 0
    Allocation d'un gros tableau non statique : [Ljava.lang.Integer;@7ba772b1
    Valeur compteur : 1
    ...
    Allocation d'un gros tableau non statique : [Ljava.lang.Integer;@3e2ddd50
    Valeur compteur : 462
    Allocation d'un gros tableau non statique : [Ljava.lang.Integer;@ffaca2c
    Valeur compteur : 463
    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at fr.memoryleak.FuiteMemoireSansStatic.<init>(FuiteMemoireSansStatic.java:14)
    at fr.memoryleak.Compteur.incrementerCompteurSansStatic(Compteur.java:25)
    at fr.memoryleak.FuiteMemoireSansStaticMain.main(FuiteMemoireSansStaticMain.java:20)

    Je rappelle qu'à base je veux prouver que :
    Si un objet est référencé par un champ statique, alors il ne sera jamais libéré.
    Avec mes exemples, je pensais pouvoir. Visiblement non. Pourquoi la version non statique abouti à "Out of Memory" ?

  7. #7
    Expert éminent
    Avatar de tchize_
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    25 482
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : Belgique

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 25 482
    Par défaut
    Pour info, ton exemple avec static est foireux. A chaque boucle, tu alloue et tu stocke en static, c'est vrai, mais tu stocke au même endroit que la version précédente => ton tableau deviens garbage collectable => tu ne saturera jamais la mémoire comme ça.

    Maintenant, il faut savoir que tu peux avoir un outofmemory, dans deux conditions:
    1) si il n'y a plus de mémoire (ce que tu essaie de démontrer)
    2) il n'y a pas assez de mémoire contigue pour allouer un grand tableau (ce sur quoi tu tombe).
    Il faut savoir que la mémoire, au fur et à mesure des allocations / libération se fragmente. Et donc la taille du plus grand bloc contigu de donnée diminue au fur et à mesure. Hors, sans static, tu essaie d'allouer un tableau de 10M, près de 60% de la taille de la JVM. Un seul objet mal placé qui découpe la zone mémoire ne deux et c'est foutu. Dans la version static, tu fais des tableaux de 5.2M, ce qui est un peu plus court.

    Donc résumons
    ton code "static" fait ceci
    Code x : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    allocation de 5.2M
    allocation de 5.2M
    5.2M deviens collectable après attribution
    allocation de 5.2M
    5.2M deviens collectable après attribution
    allocation de 5.2M
    5.2M deviens collectable après attribution
    allocation de 5.2M
    5.2M deviens collectable après attribution

    ton code non static fait ceci
    Code x : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    allocation de 10M
    10M deviens collectable quand l'objet deviens collectable
    allocation de 10M
    10M deviens collectable quand l'objet deviens collectable
    allocation de 10M
    10M deviens collectable quand l'objet deviens collectable
    allocation de 10M
    10M deviens collectable quand l'objet deviens collectable
    allocation de 10M
    10M deviens collectable quand l'objet deviens collectable
    Donc le premier est plus à l'abris des OOM puisqu'il utilise des tableau plus petit (d'ou ma première remarque la fois dernière sur le fait d'avoir une équité entre tes tests


    Voici un correction de ton code:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class BigData{
        public static int k100 = 1024*100;
        public Object[] payload;
        public BigData(){
            payload = new Object[10]; //~10*100k ~1M
            for (int i =0;i<10;i++){
                 payload[i]=new int[k100/4]; //1 int = 4 octets
            }
        }
    }
    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
    package fr.memoryleak;
     
    /**
     * Classe responsable d'une fuite mémoire (avec membre statique)
     * 
     */
    public class FuiteMemoireAvecStatic {
     
     
      private static List<BigData> bigdatas = new ArrayList<BigData>();
     
      public FuiteMemoireAvecStatic() {
        bigdatas.add(new BigData());
      }
    }
    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
    package fr.memoryleak;
     
    /**
     * Classe responsable d'une fuite mémoire (sans membre statique)
     * 
     */
    public class FuiteMemoireSansStatic {
     
     
      private List<BigData> bigdatas = new ArrayList<BigData>();
     
      public FuiteMemoireSansStatic() {
        bigdatas.add(new BigData());
      }
    }

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Créer une fuite mémoire (OutOfMemoryError: Java heap space)
    Par spiffou92 dans le forum Débuter avec Java
    Réponses: 12
    Dernier message: 03/02/2015, 13h45
  2. Fuites mémoire dans une classe "java.util.HashMap$Entry"
    Par ladyingold dans le forum Collection et Stream
    Réponses: 19
    Dernier message: 10/02/2012, 15h51
  3. [tomcat][memoire] java.net.URL et fuite mémoire
    Par Seiya dans le forum Tomcat et TomEE
    Réponses: 6
    Dernier message: 09/03/2009, 10h41
  4. Fuite Mémoire JAVA
    Par StePhAngel06 dans le forum JDBC
    Réponses: 20
    Dernier message: 10/12/2007, 14h03
  5. Réponses: 19
    Dernier message: 04/10/2006, 16h53

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo