206 votes

Que se passe-t-il lorsqu'il n'y a pas assez de mémoire pour lancer une OutOfMemoryError ?

Je suis conscient que chaque objet nécessite de la mémoire de tas et que chaque primitive/référence sur la pile nécessite de la mémoire de pile.

Lorsque je tente de créer un objet sur le tas et qu'il n'y a pas assez de mémoire pour le faire, la JVM crée un message d'erreur Erreur de mémoire (java.lang.OutOfMemoryError) sur le tas et me le lance.

Donc implicitement, cela signifie qu'il y a de la mémoire réservée par la JVM au démarrage.

Que se passe-t-il lorsque cette mémoire réservée est épuisée (elle le sera certainement, lisez la discussion ci-dessous) et que la JVM n'a pas assez de mémoire sur le tas pour créer une instance de Erreur de mémoire (java.lang.OutOfMemoryError) ?

Est-ce qu'il est juste suspendu ? Ou est-ce qu'il me lance un null puisqu'il n'y a pas de mémoire pour new une instance d'OOM ?

try {
    Object o = new Object();
    // and operations which require memory (well.. that's like everything)
} catch (java.lang.OutOfMemoryError e) {
    // JVM had insufficient memory to create an instance of java.lang.OutOfMemoryError to throw to us
    // what next? hangs here, stuck forever?
    // or would the machine decide to throw us a "null" ? (since it doesn't have memory to throw us anything more useful than a null)
    e.printStackTrace(); // e.printStackTrace() requires memory too.. =X
}

\==

Pourquoi la JVM ne pouvait-elle pas réserver suffisamment de mémoire ?

Quelle que soit la quantité de mémoire réservée, il est toujours possible que cette mémoire soit utilisée si la JVM ne dispose pas d'un moyen de "récupérer" cette mémoire :

try {
    Object o = new Object();
} catch (java.lang.OutOfMemoryError e) {
    // JVM had 100 units of "spare memory". 1 is used to create this OOM.
    try {
        e.printStackTrace();
    } catch (java.lang.OutOfMemoryError e2) {
        // JVM had 99 units of "spare memory". 1 is used to create this OOM.
        try {
            e.printStackTrace();
        } catch (java.lang.OutOfMemoryError e3) {
            // JVM had 98 units of "spare memory". 1 is used to create this OOM.
            try {
                e.printStackTrace();
            } catch (java.lang.OutOfMemoryError e4) {
                // JVM had 97 units of "spare memory". 1 is used to create this OOM.
                try {
                    e.printStackTrace();
                } catch (java.lang.OutOfMemoryError e5) {
                    // JVM had 96 units of "spare memory". 1 is used to create this OOM.
                    try {
                        e.printStackTrace();
                    } catch (java.lang.OutOfMemoryError e6) {
                        // JVM had 95 units of "spare memory". 1 is used to create this OOM.
                        e.printStackTrace();
                        //........the JVM can't have infinite reserved memory, he's going to run out in the end
                    }
                }
            }
        }
    }
}

Ou de manière plus concise :

private void OnOOM(java.lang.OutOfMemoryError e) {
    try {
        e.printStackTrace();
    } catch (java.lang.OutOfMemoryError e2) {
        OnOOM(e2);
    }
}

145voto

Buhake Sindi Points 38654

La JVM n'est jamais vraiment à court de mémoire. Elle effectue à l'avance le calcul de la mémoire de la pile du tas.

Le site Structure de la JVM, chapitre 3 Le paragraphe 3.5.2 de l'article 3.5.2 stipule que

  • Si les piles de la machine virtuelle Java peuvent être étendues de manière dynamique, et si l'expansion est tentée mais que la mémoire disponible est insuffisante pour effectuer l'expansion, ou si une mémoire insuffisante peut être rendue disponible pour créer la pile initiale de la machine virtuelle Java pour une nouvelle la machine virtuelle Java lance un message d'erreur OutOfMemoryError .

Pour Amas , section 3.5.3.

  • Si un calcul nécessite plus de tas que ce qui peut être mis à disposition par le système de gestion automatique du stockage, la machine virtuelle Java lance un OutOfMemoryError .

Il effectue donc un calcul préalable avant de procéder à l'allocation de l'objet.


Ce qui se passe, c'est que la JVM essaie d'allouer de la mémoire pour un objet dans la mémoire appelée région de génération permanente (ou PermSpace). Si l'allocation échoue (même après que la JVM ait invoqué le Garbage Collector pour essayer d'allouer de l'espace libre), elle lance un message d'erreur de type OutOfMemoryError . Même les exceptions nécessitent un espace mémoire, de sorte que l'erreur sera levée indéfiniment.

Pour en savoir plus. ? En outre, OutOfMemoryError peut se produire dans différents Structure de la JVM.

64voto

Ilmari Karonen Points 20585

Graham Borland semble avoir raison : au moins mon La JVM réutilise apparemment les OutOfMemoryErrors. Pour tester cela, j'ai écrit un programme de test simple :

class OOMTest {
    private static void test (OutOfMemoryError o) {
        try {
            for (int n = 1; true; n += n) {
                int[] foo = new int[n];
            }
        } catch (OutOfMemoryError e) {
            if (e == o)
                System.out.println("Got the same OutOfMemoryError twice: " + e);
            else test(e);
        }
    }
    public static void main (String[] args) {
        test(null);
    }
}

En l'exécutant, on obtient ce résultat :

$ javac OOMTest.java && java -Xmx10m OOMTest 
Got the same OutOfMemoryError twice: java.lang.OutOfMemoryError: Java heap space

BTW, la JVM que j'utilise (sur Ubuntu 10.04) est la suivante :

$ java -version
java version "1.6.0_26"
Java(TM) SE Runtime Environment (build 1.6.0_26-b03)
Java HotSpot(TM) 64-Bit Server VM (build 20.1-b02, mixed mode)

Edit : J'ai essayé de voir ce qui se passerait si je forcé la JVM s'exécute complètement en dehors de la mémoire en utilisant le programme suivant :

class OOMTest2 {
    private static void test (int n) {
        int[] foo;
        try {
            foo = new int[n];
            test(n * 2);
        }
        catch (OutOfMemoryError e) {
            test((n+1) / 2);
        }
    }
    public static void main (String[] args) {
        test(1);
    }
}

Il s'avère que la boucle semble interminable. Cependant, curieusement, en essayant de terminer le programme avec Ctrl + C ne fonctionne pas, mais donne seulement le message suivant :

Java HotSpot(TM) 64-Bit Server VM warning: Exception java.lang.OutOfMemoryError occurred dispatching signal SIGINT to handler- the VM may need to be forcibly terminated

41voto

Graham Borland Points 27556

La plupart des environnements d'exécution pré-allouent au démarrage, ou réservent d'une autre manière, suffisamment de mémoire pour faire face aux situations de manque de mémoire. J'imagine que la plupart des implémentations saines de la JVM le font.

23voto

benzado Points 36367

La dernière fois que j'ai travaillé en Java et que j'ai utilisé un débogueur, l'inspecteur du tas a montré que la JVM allouait une instance de OutOfMemoryError au démarrage. En d'autres termes, elle alloue l'objet avant que votre programme n'ait eu le temps de commencer à consommer de la mémoire, et encore moins d'en manquer.

12voto

Andreas_D Points 64111

À partir de la spécification de la JVM, chapitre 3.5.2 :

Si les piles de la machine virtuelle Java peuvent être étendues dynamiquement, et que l'expansion est tentée mais que la mémoire disponible est insuffisante pour effectuer l'expansion, ou si la mémoire disponible est insuffisante pour créer la pile initiale de la machine virtuelle Java pour un nouveau thread, la machine virtuelle Java lance un OutOfMemoryError .

Chaque Machine Virtuelle Java doit garantir qu'elle lancera un OutOfMemoryError . Cela implique qu'il doit être capable de créer une instance de OutOfMemoryError (ou en ayant créé un à l'avance) même s'il n'y a plus de place dans le tas.

Bien qu'il ne soit pas nécessaire de garantir qu'il reste assez de mémoire pour l'attraper et imprimer une belle trace de pile...

Addition

Vous avez ajouté du code pour montrer que la JVM pourrait manquer d'espace dans le tas si elle devait lancer plus d'un fichier OutOfMemoryError . Mais une telle mise en œuvre violerait l'exigence ci-dessus.

Il n'y a aucune obligation que les instances lancées de OutOfMemoryError sont uniques ou créés à la demande. Une JVM peut préparer exactement une instance de OutOfMemoryError pendant le démarrage et l'envoie à chaque fois qu'il n'a plus de place dans le tas - c'est-à-dire une fois, dans un environnement normal. En d'autres termes, l'instance de OutOfMemoryError que nous voyons pourrait être un singleton.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X