web-dev-qa-db-fra.com

Que se passe-t-il quand la mémoire est insuffisante pour émettre une erreur OutOfMemoryError?

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

Lorsque j'essaie de créer un objet sur le tas et que la mémoire est insuffisante, la machine virtuelle Java crée un Java.lang.OutOfMemoryError sur le tas et me le jette.

Donc, implicitement, cela signifie qu’une partie de la mémoire est réservée par la machine virtuelle au démarrage.

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

Est-ce juste accrocher? Ou est-ce qu'il me lancerait un null puisqu'il n'y a pas de mémoire pour new une instance de MOO?

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 machine virtuelle Java ne peut-elle pas réserver suffisamment de mémoire?

Quelle que soit la quantité de mémoire réservée, il est toujours possible d'utiliser cette mémoire si la machine virtuelle Java 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 plus concement:

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

La JVM ne manque jamais vraiment de mémoire. Il calcule la mémoire à l’avance.

Le Structure de la machine virtuelle Java, chapitre , section 3.5.2 indique:

  • Si les piles de machines virtuelles Java peuvent être développées de manière dynamique et que vous tentez de les développer, vous risquez de ne pas disposer d'une mémoire suffisante pour effectuer l'extension ou de créer une pile insuffisante pour créer la pile de machines virtuelles Java initiale. pour un nouveau thread, la machine virtuelle Java lève un OutOfMemoryError.

Pour Tas , Section 3.5.3.

  • Si un calcul nécessite plus de tas que le système de gestion automatique du stockage ne peut en fournir, la machine virtuelle Java lève un OutOfMemoryError.

Donc, il fait un calcul à l’avance avant d’allouer l’objet.


En réalité, la machine virtuelle Java tente d'allouer de la mémoire à un objet de la mémoire appelé région de génération permanente (ou PermSpace). Si l'allocation échoue (même après que la JVM a appelé le collecteur de place pour essayer et allouer de l'espace libre), elle lance un OutOfMemoryError. Même les exceptions nécessitent un espace mémoire afin que l'erreur soit renvoyée indéfiniment.

Lectures complémentaires. ? De plus, OutOfMemoryError peut apparaître dans différentes structure JVM.

145
Buhake Sindi

Graham Borland semble avoir raison : au moins mon La machine virtuelle Java réutilise apparemment 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);
    }
}

Son exécution produit cette sortie:

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

BTW, la machine virtuelle 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 machine virtuelle Java à manquer de mémoire en utilisant les éléments suivants programme:

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);
    }
}

En fin de compte, cela semble boucler pour toujours. Cependant, curieusement, en essayant de terminer le programme avec Ctrl+C ne fonctionne pas, mais ne donne que 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

64
Ilmari Karonen

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 gérer les situations de manque de mémoire. J'imagine que la plupart des implémentations sensées de JVM le feraient.

41
Graham Borland

La dernière fois que je travaillais dans Java et en utilisant un débogueur, l'inspecteur de tas a montré que la machine virtuelle a alloué une instance de OutOfMemoryError au démarrage. En d'autres termes, il alloue l'objet avant que votre programme ait une chance pour commencer à consommer, et encore moins à court de mémoire.

23
benzado

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

Si les piles de machines virtuelles Java peuvent être développées de manière dynamique et que vous tentez de les développer, vous risquez de ne pas disposer d'une mémoire suffisante pour effectuer l'extension ou de créer une pile insuffisante pour créer la pile de machines virtuelles Java initiale. pour un nouveau thread, la machine virtuelle Java lève un OutOfMemoryError.

Chaque Java machine virtuelle doit garantir qu’elle jettera un OutOfMemoryError. Cela implique qu’il doit être capable de créer une instance de OutOfMemoryError (ou d’avoir été créé à l’avance) même s’il n’ya plus d’espace de mémoire.

Bien qu'il ne soit pas nécessaire de garantir qu'il reste suffisamment de mémoire pour l'attraper et imprimer un stacktrace de Nice ...

Ajout

Vous avez ajouté du code pour montrer que la machine virtuelle peut manquer d’espace si elle doit lancer plus d’un OutOfMemoryError. Mais une telle mise en œuvre violerait l'exigence d'en haut.

Il n'est pas nécessaire que les instances projetées de OutOfMemoryError soient uniques ou créées à la demande. Une machine virtuelle Java peut préparer exactement une instance de OutOfMemoryError au démarrage et la lancer chaque fois qu’elle manque d’espace de mémoire - une fois, dans un environnement normal. En d'autres termes: l'instance de OutOfMemoryError que nous voyons pourrait être un singleton.

12
Andreas_D

Question interessante :-). Alors que les autres ont donné de bonnes explications sur les aspects théoriques, j'ai décidé de l'essayer. C'est sur Oracle JDK 1.6.0_26, Windows 7 64 bits.

Configuration du test

J'ai écrit un programme simple pour épuiser la mémoire (voir ci-dessous).

Le programme crée simplement un statique Java.util.List Et continue à y insérer de nouvelles chaînes jusqu'à ce que le MOO soit lancé. Il l'attrape ensuite et continue de se fourrer dans une boucle sans fin (JVM médiocre ...).

Résultat du test

Comme on peut le voir à la sortie, les quatre premières fois que OOME est lancé sont livrés avec une trace de pile. Après cela, les OOME suivants n'impriment que Java.lang.OutOfMemoryError: Java heap space Si printStackTrace() est appelée.

Donc, apparemment, la machine virtuelle Java s'efforce d'imprimer une trace de pile si elle le peut, mais si la mémoire est vraiment saturée, elle omet simplement la trace, comme le suggèrent les autres réponses.

Le code de hachage de l’OMEH est également intéressant. Notez que les premiers OOME ont tous des hachages différents. Une fois que la machine virtuelle Java commence à omettre les traces de pile, le hachage est toujours le même. Cela suggère que la machine virtuelle Java utilisera des instances OOME fraîches (préallouées?) Aussi longtemps que possible, mais si Push venait à être transféré, il ne ferait que réutiliser la même instance au lieu de ne rien lancer.

sortie

Remarque: j'ai tronqué certaines traces de pile pour faciliter la lecture de la sortie ("[...]").

iteration 0
iteration 100000
iteration 200000
iteration 300000
iteration 400000
iteration 500000
iteration 600000
iteration 700000
iteration 800000
iteration 900000
iteration 1000000
iteration 1100000
iteration 1200000
iteration 1300000
iteration 1400000
iteration 1500000
Ouch: Java.lang.OutOfMemoryError: Java heap space; hash: 1069480624
Keep on trying...
Java.lang.OutOfMemoryError: Java heap space
    at Java.util.Arrays.copyOf(Unknown Source)
    at Java.util.Arrays.copyOf(Unknown Source)
    at Java.util.ArrayList.ensureCapacity(Unknown Source)
    at Java.util.ArrayList.add(Unknown Source)
    at testsl.Div.gobbleUpMemory(Div.Java:23)
    at testsl.Div.exhaustMemory(Div.Java:12)
    at testsl.Div.main(Div.Java:7)
Java.lang.OutOfMemoryError: Java heap space
    at Java.util.Arrays.copyOf(Unknown Source)
[...]
Ouch: Java.lang.OutOfMemoryError: Java heap space; hash: 616699029
Keep on trying...
Java.lang.OutOfMemoryError: Java heap space
    at Java.util.Arrays.copyOf(Unknown Source)
[...]
Ouch: Java.lang.OutOfMemoryError: Java heap space; hash: 2136955031
Keep on trying...
Java.lang.OutOfMemoryError: Java heap space
    at Java.util.Arrays.copyOf(Unknown Source)
[...]
Ouch: Java.lang.OutOfMemoryError: Java heap space; hash: 1535562945
Keep on trying...
Java.lang.OutOfMemoryError: Java heap space
Ouch: Java.lang.OutOfMemoryError: Java heap space; hash: 1734048134
Keep on trying...
Ouch: Java.lang.OutOfMemoryError: Java heap space; hash: 1734048134
Keep on trying...
Java.lang.OutOfMemoryError: Java heap space
Ouch: Java.lang.OutOfMemoryError: Java heap space; hash: 1734048134
Keep on trying...
[...]

Le programme

public class Div{
    static Java.util.List<String> list = new Java.util.ArrayList<String>();

    public static void main(String[] args) {
        exhaustMemory();
    }

    private static void exhaustMemory() {
        try {
            gobbleUpMemory();
        } catch (OutOfMemoryError e) {
            System.out.println("Ouch: " + e+"; hash: "+e.hashCode());
            e.printStackTrace();
            System.out.println("Keep on trying...");
            exhaustMemory();
        }
    }

    private static void gobbleUpMemory() {
        for (int i = 0; i < 10000000; i++) {
            list.add(new String("some random long string; use constructor to force new instance"));
            if (i % 10000000== 0) {
                System.out.println("iteration "+i);
            }
        }

    }
}
11
sleske

Je suis à peu près sûr que la machine virtuelle s'assurera qu'elle dispose d'au moins assez de mémoire pour émettre une exception avant qu'elle ne manque de mémoire.

6
Oscar Gomez

Les exceptions indiquant une tentative de violation des limites d'un environnement à mémoire gérée sont gérées par l'exécution dudit environnement, dans ce cas la machine virtuelle Java. La JVM est son propre processus, qui exécute le IL de votre application. Si un programme tente de passer un appel qui étend la pile d'appels au-delà des limites ou alloue plus de mémoire que la JVM ne peut en réserver, le moteur d'exécution lui-même injectera une exception, ce qui entraînera le dévidage de la pile d'appels. Indépendamment de la quantité de mémoire dont votre programme a actuellement besoin ou de la profondeur de sa pile d'appels, la machine virtuelle Java aura alloué suffisamment de mémoire dans ses propres limites de processus pour créer ladite exception et l'injecter dans votre code.

4
KeithS

Pour clarifier davantage la réponse de @Graham Borland, la JVM fait ceci au démarrage:

private static final OutOfMemoryError OOME = new OutOfMemoryError();

Plus tard, la machine virtuelle Java exécute l’un des bytecodes Java: 'new', 'anewarray' ou 'multianwarray'. Cette instruction l’oblige à exécuter un certain nombre d’étapes de mémoire insuffisante. état:

  1. Invoquez une fonction native, dites allocate(). allocate() tente d'allouer de la mémoire à une nouvelle instance d'une classe ou d'un tableau particulier.
  2. Cette demande d'allocation échouant, la machine virtuelle Java appelle une autre fonction native, telle que doGC(), qui tente d'effectuer un nettoyage de la mémoire.
  3. Lorsque cette fonction revient, allocate() essaie d'allouer de nouveau la mémoire pour l'instance.
  4. Si cela échoue (*), la machine virtuelle Java, au sein de allocate (), effectue simplement un throw OOME;, En référence à l'OOME qu'elle a instanciée au démarrage. Notez qu'il n'a pas dû attribuer cet OOME, il s'y réfère simplement.

Évidemment, ce ne sont pas des étapes littérales; ils vont varier d’une machine à l’autre en matière d’implémentation, mais c’est l’idée de haut niveau.

(*) Une quantité importante de travail se passe ici avant d’échouer. La machine virtuelle Java tentera d'effacer les objets SoftReference, de tenter une allocation directement dans la génération permanente lors de l'utilisation d'un collecteur de génération, et éventuellement d'autres éléments, tels que la finalisation.

4
ahawtho

Vous semblez confondre la mémoire virtuelle réservée par la machine virtuelle Java dans laquelle celle-ci exécute des programmes Java) avec la mémoire native du système d'exploitation hôte dans laquelle la machine virtuelle est exécutée en tant que processus natif. La machine virtuelle Java de votre ordinateur s’exécute dans la mémoire gérée par le système d’exploitation, pas dans la mémoire réservée par la machine virtuelle Java pour exécuter les programmes Java.

Lectures complémentaires:

Et comme note finale, essayez d’attraper un Java.lang.Error (et ses classes descendantes) pour imprimer un stacktrace peut ne vous donner aucune information utile. Vous voulez plutôt un vidage de tas.

4
Michael Tiffany

Les réponses indiquant que la JVM pré-allouera OutOfMemoryErrors sont en effet correctes.
En plus de tester cela en provoquant une situation de mémoire insuffisante, nous pouvons simplement vérifier le tas de toute machine virtuelle (j'ai utilisé un petit programme qui ne fait que dormir, l'exécutant à l'aide de la machine virtuelle Java Hotspot de Java 8 mise à jour 31).

En utilisant jmap, nous voyons qu'il semble y avoir 9 instances de OutOfMemoryError (même si nous avons beaucoup de mémoire):

> jmap -histo 12103 | grep OutOfMemoryError 
 71: 9 288 erreur Java.lang.OutOfMemoryError 
 170: 1 32 [Ljava.lang.OutOfMemoryError; 

Nous pouvons alors générer un vidage de tas:

> jmap -dump: format = b, fichier = heap.hprof 12315 

et ouvrez-le à l'aide de Eclipse Memory Analyzer , où une requête OQL montre que la machine virtuelle Java semble pré-allouer OutOfMemoryErrors pour tous les messages possibles:

enter image description here

Le code de la = Java 8 machine virtuelle Java à point névralgique qui préalloue en fait ces se trouve ici et ressemble à ceci (avec certaines parties omises):

...
// Setup preallocated OutOfMemoryError errors
k = SystemDictionary::resolve_or_fail(vmSymbols::Java_lang_OutOfMemoryError(), true, CHECK_false);
k_h = instanceKlassHandle(THREAD, k);
Universe::_out_of_memory_error_Java_heap = k_h->allocate_instance(CHECK_false);
Universe::_out_of_memory_error_metaspace = k_h->allocate_instance(CHECK_false);
Universe::_out_of_memory_error_class_metaspace = k_h->allocate_instance(CHECK_false);
Universe::_out_of_memory_error_array_size = k_h->allocate_instance(CHECK_false);
Universe::_out_of_memory_error_gc_overhead_limit =
  k_h->allocate_instance(CHECK_false);

...

if (!DumpSharedSpaces) {
  // These are the only Java fields that are currently set during shared space dumping.
  // We prefer to not handle this generally, so we always reinitialize these detail messages.
  Handle msg = Java_lang_String::create_from_str("Java heap space", CHECK_false);
  Java_lang_Throwable::set_message(Universe::_out_of_memory_error_Java_heap, msg());

  msg = Java_lang_String::create_from_str("Metaspace", CHECK_false);
  Java_lang_Throwable::set_message(Universe::_out_of_memory_error_metaspace, msg());
  msg = Java_lang_String::create_from_str("Compressed class space", CHECK_false);
  Java_lang_Throwable::set_message(Universe::_out_of_memory_error_class_metaspace, msg());

  msg = Java_lang_String::create_from_str("Requested array size exceeds VM limit", CHECK_false);
  Java_lang_Throwable::set_message(Universe::_out_of_memory_error_array_size, msg());

  msg = Java_lang_String::create_from_str("GC overhead limit exceeded", CHECK_false);
  Java_lang_Throwable::set_message(Universe::_out_of_memory_error_gc_overhead_limit, msg());

  msg = Java_lang_String::create_from_str("/ by zero", CHECK_false);
  Java_lang_Throwable::set_message(Universe::_arithmetic_exception_instance, msg());

  // Setup the array of errors that have preallocated backtrace
  k = Universe::_out_of_memory_error_Java_heap->klass();
  assert(k->name() == vmSymbols::Java_lang_OutOfMemoryError(), "should be out of memory error");
  k_h = instanceKlassHandle(THREAD, k);

  int len = (StackTraceInThrowable) ? (int)PreallocatedOutOfMemoryErrorCount : 0;
  Universe::_preallocated_out_of_memory_error_array = oopFactory::new_objArray(k_h(), len, CHECK_false);
  for (int i=0; i<len; i++) {
    oop err = k_h->allocate_instance(CHECK_false);
    Handle err_h = Handle(THREAD, err);
    Java_lang_Throwable::allocate_backtrace(err_h, CHECK_false);
    Universe::preallocated_out_of_memory_errors()->obj_at_put(i, err_h());
  }
  Universe::_preallocated_out_of_memory_error_avail_count = (jint)len;
}
...

et ce code indique que la machine virtuelle Java essaiera d'abord d'utiliser l'une des erreurs pré-allouées avec de l'espace pour une trace de pile, puis qu'elle reviendra à une erreur sans trace de pile:

oop Universe::gen_out_of_memory_error(oop default_err) {
  // generate an out of memory error:
  // - if there is a preallocated error with backtrace available then return it wth
  //   a filled in stack trace.
  // - if there are no preallocated errors with backtrace available then return
  //   an error without backtrace.
  int next;
  if (_preallocated_out_of_memory_error_avail_count > 0) {
    next = (int)Atomic::add(-1, &_preallocated_out_of_memory_error_avail_count);
    assert(next < (int)PreallocatedOutOfMemoryErrorCount, "avail count is corrupt");
  } else {
    next = -1;
  }
  if (next < 0) {
    // all preallocated errors have been used.
    // return default
    return default_err;
  } else {
    // get the error object at the slot and set set it to NULL so that the
    // array isn't keeping it alive anymore.
    oop exc = preallocated_out_of_memory_errors()->obj_at(next);
    assert(exc != NULL, "slot has been used already");
    preallocated_out_of_memory_errors()->obj_at_put(next, NULL);

    // use the message from the default error
    oop msg = Java_lang_Throwable::message(default_err);
    assert(msg != NULL, "no message");
    Java_lang_Throwable::set_message(exc, msg);

    // populate the stack trace and return it.
    Java_lang_Throwable::fill_in_stack_trace_of_preallocated_backtrace(exc);
    return exc;
  }
}
3
Johan Kaving