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

Java Discussion :

Java et la gestion de la mémoire


Sujet :

Java

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre chevronné Avatar de Claythest
    Profil pro
    Inscrit en
    Mai 2003
    Messages
    558
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2003
    Messages : 558
    Par défaut Java et la gestion de la mémoire
    Bonjour tout le monde,

    Quelque chose m'échappe dans la gestion de la mémoire de Java. Je ne comprends pas pourquoi j'obtiens une OutOfMemoryError alors qu'il semble pourtant qu'il y ai assez de mémoire disponible.

    Voici un exemple de code générant un OutOfMemoryError (avec les paramètres Xms et Xmx par défaut) :
    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
    package testheapsize;
     
    import java.util.logging.Level;
    import java.util.logging.Logger;
     
    public class Main {
     
        public static void main(String... args) {
            try {
                long size = 178868493;
     
                Runtime runtime = Runtime.getRuntime();
                long free0 = runtime.freeMemory();
                System.out.println(size + " : " + free0 + " / " + runtime.totalMemory());
                byte[] fileBytes = new byte[(int) size];
                long free1 = runtime.freeMemory();
                System.out.println(size + " : " + free1 + " / " + runtime.totalMemory());
            } catch (Exception ex) {
                Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
    Ce que j'obtiens donc est la chose suivante :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    178868493 : 15964944 / 16252928
    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
            at testheapsize.Main.main(Main.java:15)
    Java Result: 1
    Soit, pourquoi pas.

    Cependant, si au lieu de 178868493 je mets 178868492 (donc 1 octet de différence) :
    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
    package testheapsize;
     
    import java.util.logging.Level;
    import java.util.logging.Logger;
     
    public class Main {
     
        public static void main(String... args) {
            try {
                long size = 178868492;
     
                Runtime runtime = Runtime.getRuntime();
                long free0 = runtime.freeMemory();
                System.out.println(size + " : " + free0 + " / " + runtime.totalMemory());
                byte[] fileBytes = new byte[(int) size];
                long free1 = runtime.freeMemory();
                System.out.println(size + " : " + free1 + " / " + runtime.totalMemory());
            } catch (Exception ex) {
                Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
    J'obtiens alors le résultat suivant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    178868492 : 15964944 / 16252928
    178868492 : 79111016 / 259522560
    J'ai donc 79111016 octets de disponible après création de mon tableau. Or dans le premier cas où j'augmente sa taille de seulement 1, la JVM me dit qu'elle n'a pas assez de place !

    Quelque chose m'échappe. Quelqu'un peut-il m'aiguiller sur les raisons de ce fonctionnement ?

    Merci d'avance !

  2. #2
    Expert éminent
    Avatar de adiGuba
    Homme Profil pro
    Développeur Java/Web
    Inscrit en
    Avril 2002
    Messages
    13 938
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Java/Web
    Secteur : Transports

    Informations forums :
    Inscription : Avril 2002
    Messages : 13 938
    Billets dans le blog
    1
    Par défaut
    Salut,



    Si tu es en Java 5 ou +, utilises les MXBean pour afficher l'état de la mémoire (c'est plus détaillé) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
                MemoryMXBean mx = ManagementFactory.getMemoryMXBean();
                System.out.println(mx.getHeapMemoryUsage());

    Sinon essayes de lancer ton code avec -XX:+PrintGC pour afficher les info du GC, ca pourrait peut-être aider...


    a++

  3. #3
    Membre chevronné Avatar de Claythest
    Profil pro
    Inscrit en
    Mai 2003
    Messages
    558
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2003
    Messages : 558
    Par défaut
    Merci pour MemoryMXBean mx = ManagementFactory.getMemoryMXBean(); je ne connaissais pas, de même pour l'option -XX:+PrintGC...

    Cela dit, je n'ai pas l'impression que ça grand chose à ma compréhension...

    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
    package testheapsize;
     
    import java.lang.management.ManagementFactory;
    import java.lang.management.MemoryMXBean;
    import java.util.logging.Level;
    import java.util.logging.Logger;
     
    public class Main {
     
        public static void main(String... args) {
            try {
                long size = 178867868;
     
                MemoryMXBean mx = ManagementFactory.getMemoryMXBean();
                System.out.println(mx.getHeapMemoryUsage());
                byte[] fileBytes = new byte[(int) size];
                System.out.println(mx.getHeapMemoryUsage());
            } catch (Exception ex) {
                Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
    Affiche cela :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    init = 16777216(16384K) used = 288120(281K) committed = 16252928(15872K) max = 259522560(253440K)
    [GC 281K->121K(15872K), 0.4281949 secs]
    [Full GC 121K->121K(15872K), -0.4138760 secs]
    [Full GC 121K->108K(179776K), 0.0099123 secs]
    init = 16777216(16384K) used = 180411544(176183K) committed = 259522560(253440K) max = 259522560(253440K)
    Si je comprends bien, j'ai donc le "commited" (qui est bien égal au max) moins le "used" de disponible après création de mon tableau de bytes, soit 259522560 - 180411544 = environ 80 000 000 octets.
    Pourtant, si j'augmente de 1 ma taille du tableau (de 178867868 à 178867869 - la valeur a changé par rapport au test de mon post initial, mais ça se comprend par la taille prise par l'objet mx) :
    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
    package testheapsize;
     
    import java.lang.management.ManagementFactory;
    import java.lang.management.MemoryMXBean;
    import java.util.logging.Level;
    import java.util.logging.Logger;
     
    public class Main {
     
        public static void main(String... args) {
            try {
                long size = 178867869;
     
                MemoryMXBean mx = ManagementFactory.getMemoryMXBean();
                System.out.println(mx.getHeapMemoryUsage());
                byte[] fileBytes = new byte[(int) size];
                System.out.println(mx.getHeapMemoryUsage());
            } catch (Exception ex) {
                Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
    J'obtiens :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    init = 16777216(16384K) used = 288120(281K) committed = 16252928(15872K) max = 259522560(253440K)
    [GC 281K->121K(15872K), -0.4252731 secs]
    [Full GC 121K->121K(15872K), 0.4393221 secs]
    [Full GC 121K->108K(179776K), 0.0096222 secs]
    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
            at testheapsize.Main.main(Main.java:16)
    Java Result: 1
    Donc toujours dans l'incompréhension...

    En tout cas, merci de t'intéresser à mon sujet

  4. #4
    Membre chevronné Avatar de Claythest
    Profil pro
    Inscrit en
    Mai 2003
    Messages
    558
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2003
    Messages : 558
    Par défaut
    Je précise que je ne cherche pas à comprendre cela pour le côté "sportif" de la chose, mais parce que j'ai une application qui demande à charger un gros fichier en mémoire et qui nécessite un heap size énorme comparé à la taille du fichier à charger...

    Donc j'aimerai comprendre cela avant de penser à une autre solution...

  5. #5
    Expert éminent
    Avatar de adiGuba
    Homme Profil pro
    Développeur Java/Web
    Inscrit en
    Avril 2002
    Messages
    13 938
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Java/Web
    Secteur : Transports

    Informations forums :
    Inscription : Avril 2002
    Messages : 13 938
    Billets dans le blog
    1
    Par défaut
    Après être allé à la pèche aux infos, je suis tomber là dessus : http://stackoverflow.com/questions/7...mory-available

    En gros les tableaux doivent être alloué sur un même bloc. Le problème c'est que le heap est déjà découpé en plusieurs parties, et donc plus le tableau est grand plus il est dur de lui trouver une place. Là ton tableau correspond quand même à presque 70% de ton heap...


    Après à 1 octets près c'est pas de bol !!!
    Mais ce doit être une limite "arbitraire" (d'ailleurs perso je ne reproduit pas le problème. Enfin pas avec les mêmes valeurs).


    Citation Envoyé par Claythest Voir le message
    Je précise que je ne cherche pas à comprendre cela pour le côté "sportif" de la chose, mais parce que j'ai une application qui demande à charger un gros fichier en mémoire et qui nécessite un heap size énorme comparé à la taille du fichier à charger...
    Tu as donc plusieurs solutions :
    • Augmenter ton max-heap-size.
    • Traiter ton fichier par bloc, pour éviter de la charger entièrement en mémoire.
    • Utiliser des ByteBuffer avec allocateDirect(), qui n'utilise pas le heap.



    a++

  6. #6
    Membre chevronné Avatar de Claythest
    Profil pro
    Inscrit en
    Mai 2003
    Messages
    558
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2003
    Messages : 558
    Par défaut
    Merci énormément pour cette trouvaille !

    Après à 1 octets près c'est pas de bol !!!
    J'ai fait exprès de trouver cette limite afin d'arriver à ce qui me paraissait absurde (à 1 octet près, un coup j'ai plus de mémoire, un coup j'ai 80Mo de disponible...) dans mon environnement. Je pense que cette limite dépend du compilateur, de la machine (32 bits / 64 bits) et de la JVM... Mais au fond peu importe la valeur... D'ailleurs entre les 2 bouts de codes (sans et avec le MemoryMXBean ), cette valeur a changé.

    Concernant les solutions :
    - Augmenter le heap size
    => Le problème c'est qu'aujourd'hui c'est un fichier de 200 Mo qui me pose problème, mais demain si un client me sort un fichier de 1 Go (et ça pourrait arriver), il me faudrait beaucoup plus de RAM que nécessaire, or sur une machine 32 bits le max heap size est aux environ de 1,7Go, ce qui pourrait ne pas suffire.

    - Traiter ton fichier par bloc, pour éviter de la charger entièrement en mémoire.
    => En fait c'est ce que je faisais avant, mais pour des raisons de performances, et pensant que tant qu'il y a de la place dans le tas, on peut charger le fichier intégralement en RAM, je pensais avoir trouvé la solution miracle. Donc en effet, je vais peut être devoir faire machine arrière.

    - Utiliser des ByteBuffer avec allocateDirect(), qui n'utilise pas le heap.
    Alors ça je ne connaissais pas, et je vais me pencher dessus tout de suite ! Je ne savais pas qu'il était possible de "sortir" du heap...

    Donc merci pour ton aide précieuse (comme d'habitude d'ailleurs ) !

    Et petite question subsidiaire : le garbage collector ne défragmente pas le tas si besoin je me trompe ? Car si c'est bien le cas, cela veut en effet dire qu'il est plus que déconseillé de créer de gros tableaux pendant que l'application "vie" ( = plusieurs objets ont été créés, la mémoire est donc potentiellement fortement fragmentée). Moi qui pensait (naïvement certes :p) que tant qu'il y avait de la place, ça ne posait pas de soucis...

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

Discussions similaires

  1. Gestion de la mémoire en Java
    Par scratch_1 dans le forum Général Java
    Réponses: 9
    Dernier message: 06/10/2009, 15h15
  2. Réponses: 13
    Dernier message: 14/02/2008, 13h27
  3. Gestion des variables - mémoire ?
    Par RIVOLLET dans le forum Langage
    Réponses: 4
    Dernier message: 26/10/2002, 12h44

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