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

207

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

Lorsque j'essaie de créer un objet sur le tas et que la mémoire est insuffisante pour cela, la JVM crée une java.lang.OutOfMemoryError sur le tas et me la 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 utilisée (elle serait certainement utilisée, 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 java.lang.OutOfMemoryError ?

Est-ce que ça se bloque? Ou me lancerait-il nullcar il n'y a pas de mémoire dans newune 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 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 d'utiliser cette mémoire si la JVM n'a pas de 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 précisément:

private void OnOOM(java.lang.OutOfMemoryError e) {
    try {
        e.printStackTrace();
    } catch (java.lang.OutOfMemoryError e2) {
        OnOOM(e2);
    }
}
Pacerier
la source
2
votre réponse dépendra dans une large mesure de la
machine virtuelle Java
23
Une bibliothèque de téléphonie que j'ai utilisée (dans les années 90) pour attraper OutOfMemoryExceptionpuis faire quelque chose qui impliquait la création d'un grand tampon ...
Tom Hawtin - tackline
@ TomHawtin-tackline Et si les opérations impliquées dans cette opération lançaient un autre MOO?
Pacerier
38
Comme un téléphone portable, il est à court de batterie mais il a suffisamment de batterie pour continuer à envoyer du spam "Vous êtes à court de batterie".
Kazuma
1
"Que se passe-t-il lorsque cette mémoire réservée est utilisée": cela ne peut arriver que si le programme intercepte la première OutOfMemoryErroret y conserve une référence. Il s'avère que la capture d'un OutOfMemoryErrorn'est pas aussi utile qu'on pourrait le penser, car vous ne pouvez garantir presque rien sur l'état de votre programme lors de sa capture. Voir stackoverflow.com/questions/8728866/…
Raedwald

Réponses:

145

La JVM ne manque jamais vraiment de mémoire. Il effectue à l'avance le calcul de la mémoire de la pile de segments de mémoire.

La structure de la JVM, chapitre 3 , section 3.5.2 indique:

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

Pour Heap , 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.

Donc, il fait un calcul à l'avance avant de faire l'allocation de l'objet.


Ce qui se passe, c'est que la JVM essaie 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 invoqué le garbage collector pour essayer d'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, cela OutOfMemoryErrorpeut se produire dans différentes structures JVM.

Buhake Sindi
la source
10
Je veux dire oui, mais la machine virtuelle Java n'aurait-elle pas également besoin de mémoire pour lancer une OutOfMemoryError? Que se passe-t-il lorsqu'il n'y a pas de mémoire pour lancer un MOO?
Pacerier
5
Mais si la machine virtuelle Java ne renvoie pas la référence à la même instance d'un MOO, n'êtes-vous pas d'accord qu'elle finirait par manquer de mémoire réservée? (comme démontré dans le code de la question)
Pacerier
1
Permettez-moi de mettre une référence au commentaire de Graham ici: stackoverflow.com/questions/9261705/…
Pacerier
2
Cela pourrait être bien si la machine virtuelle conservait un singleton d'exception de MOO pour le cas extrême indiqué, et dans une centrale nucléaire.
John K
8
@JohnK: J'espère que les centrales nucléaires ne sont pas programmées à Java, tout comme les navettes spatiales et les Boeing 757 ne sont pas programmés à Java.
Dietrich Epp
64

Graham Borland semble avoir raison : au moins ma JVM 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);
    }
}

L'exécuter produit cette sortie:

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

BTW, la JVM que j'exécute (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 forçais la JVM à manquer complètement de 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);
    }
}

En fin de compte, il semble boucler pour toujours. Cependant, curieusement, essayer de terminer le programme avec Ctrl+ Cne 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

Ilmari Karonen
la source
Joli test, pareil pour moi avec la version "1.7.0_01" de la machine virtuelle Java HotSpot (TM) 64 bits du serveur
Pacerier
Intéressant. Cela donne l'impression que la JVM ne comprend pas complètement la récursivité de la queue ... (Comme si elle faisait une réutilisation de pile récursive de la queue, mais pas assez pour nettoyer toute la mémoire ...)
Izkata
Vous avez modifié votre code pour voir combien de codes différents j'obtiens - je fais toujours exécuter la branche else exactement 5 fois.
Irfy
@Izkata: Je dirais plutôt que c'est une décision consciente de pré-allouer des nMOO et de réutiliser l'un d'eux par la suite, afin qu'un MOO puisse toujours être lancé. La JVM de Sun / Oracle ne prend pas du tout en charge la récursivité de queue IIRC?
Irfy
10
@Izkata La boucle s'exécute apparemment sans fin car la JVM lance constamment un seul et même (5e) MOO après qu'il manque de mémoire. Il a donc des ncadres sur la pile et finit par créer et détruire des cadres n+1pour l'éternité, donnant l'impression de fonctionner sans fin.
Irfy
41

La plupart des environnements d'exécution pré-alloueront au démarrage, ou réserveront autrement, suffisamment de mémoire pour faire face aux situations de famine de mémoire. J'imagine que la plupart des implémentations JVM sensées feraient cela.

Graham Borland
la source
1
stackoverflow.com/questions/9261215/… : Vrai mais si la machine virtuelle Java a réservé 100 unités de mémoire pour ce faire et a dépensé 1 unité sur le premier MOO, que se passe-t-il si dans mon bloc de capture MOO je fais e.printStackTrace ()? e.printStackTrace () nécessite également de la mémoire. Ensuite, la JVM dépenserait une autre unité de mémoire pour me lancer un autre MOO (98 unités restantes) et je l'attrape avec un e.printStackTrace () donc la JVM me lance un autre MOO (97 unités restantes) et bien cela est pris et je voulais ..
Pacerier
3
C'est exactement pourquoi OOME n'incluait jamais de trace de pile - les traces de pile prennent de la mémoire! OOME n'a commencé à essayer d'inclure une trace de pile dans java 6 ( blogs.oracle.com/alanb/entry/… ). Je suppose que si la trace de pile n'est pas possible, l'exception est levée sans trace de pile.
Sean Reilly
1
@SeanReilly Je veux dire qu'une exception qui n'a pas de trace de pile est toujours un objet, qui nécessite toujours de la mémoire. La mémoire est nécessaire, qu'une trace de pile soit fournie ou non. Est-il vrai que dans le bloc catch s'il n'y a plus de mémoire pour créer un MOO (pas de mémoire pour en créer un même sans trace de pile), alors j'attraperais un null?
Pacerier
17
La machine virtuelle Java peut renvoyer plusieurs références à une seule instance statique de l'exception OOM. Ainsi, même si votre catchclause tente d'utiliser plus de mémoire, la machine virtuelle Java peut simplement continuer à lancer la même instance de MOO encore et encore.
Graham Borland
1
@TheEliteGentleman Je suis d'accord que ce sont aussi de très bonnes réponses, mais la JVM vit sur une machine physique. Ces réponses n'ont pas expliqué comment la JVM pouvait magiquement avoir suffisamment de mémoire pour toujours fournir une instance de MOO. "C'est toujours la même instance" semble résoudre l'énigme.
Pacerier
23

La dernière fois que je travaillais en Java et que j'utilisais un débogueur, l'inspecteur de tas a montré que la JVM avait alloué une instance de OutOfMemoryError au démarrage. En d'autres termes, il alloue l'objet avant que votre programme ne commence à consommer, et encore moins à manquer de mémoire.

benzado
la source
12

Dans la spécification JVM, chapitre 3.5.2:

Si les piles de machines virtuelles Java peuvent être développées dynamiquement et que l'expansion est tentée mais que la mémoire insuffisante peut être mise à disposition pour effectuer l'expansion, ou si la mémoire insuffisante peut être mise à disposition pour créer la pile de machines virtuelles Java initiale pour un nouveau thread, Java virtual la machine lance unOutOfMemoryError .

Chaque machine virtuelle Java doit garantir qu'elle lancera un fichier OutOfMemoryError. Cela implique qu'il doit être capable de créer une instance deOutOfMemoryError (ou de l'avoir créé à l'avance) même s'il n'y a plus d'espace de stockage.

Bien qu'il ne doive pas garantir, qu'il reste suffisamment de mémoire pour l'attraper et imprimer une belle trace de pile ...

Une addition

Vous avez ajouté du code pour montrer que la JVM risque de manquer d'espace de tas si elle devait en lancer plusieurs OutOfMemoryError . Mais une telle mise en œuvre violerait l'exigence d'en haut.

Il n'est pas nécessaire que les instances levées OutOfMemoryErrorsoient uniques ou créées à la demande. Une machine virtuelle Java peut préparer exactement une instance de OutOfMemoryErrorlors du démarrage et la lancer chaque fois qu'elle manque d'espace de stockage - ce qui est une fois, dans un environnement normal. En d'autres termes: l'instance OutOfMemoryErrorque nous voyons pourrait être un singleton.

Andreas Dolk
la source
Je suppose que pour l'implémenter, il faudrait s'abstenir d'enregistrer la trace de pile, si l'espace était restreint.
Raedwald
@ Raedwald: En fait, c'est exactement ce que fait la machine virtuelle Oracle, voir ma réponse.
sleske
11

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 de test

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

Le programme ne fait que créer un statique java.util.Listet continue à y bourrer de nouvelles chaînes jusqu'à ce que le MOO soit lancé. Il le rattrape ensuite et continue le bourrage dans une boucle sans fin (pauvre JVM ...).

Résultat du test

Comme on peut le voir sur la sortie, les quatre premières fois que OOME est lancé, il est livré avec une trace de pile. Après cela, les OOME suivants ne s'impriment que java.lang.OutOfMemoryError: Java heap spacesi printStackTrace()est invoqué.

Donc, apparemment, la JVM fait un effort pour imprimer une trace de pile si elle le peut, mais si la mémoire est vraiment serrée, elle omet simplement la trace, comme le suggèrent les autres réponses.

Le code de hachage de OOME est également intéressant. Notez que les premiers OOME ont tous des hachages différents. Une fois que la JVM commence à omettre les traces de pile, le hachage est toujours le même. Cela suggère que la JVM utilisera des instances OOME fraîches (préallouées?) Aussi longtemps que possible, mais si la poussée vient à bout, elle réutilisera simplement la même instance plutôt que de n'avoir rien à jeter.

Production

Remarque: J'ai tronqué certaines traces de pile pour rendre la sortie plus facile à lire ("[...]").

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

    }
}
sleske
la source
Lorsque push arrive, il ne peut pas allouer de mémoire pour OOME, il vide donc ceux déjà créés.
Buhake Sindi
1
Note mineure: une partie de votre production semble être hors séquence, probablement parce que vous imprimez à System.outmais des printStackTrace()utilisations System.errpar défaut. Vous obtiendrez probablement de meilleurs résultats en utilisant l'un ou l'autre flux de manière cohérente.
Ilmari Karonen
@IlmariKaronen: Oui, je l'ai remarqué. Ce n'est qu'un exemple, donc cela n'avait pas d'importance. Évidemment, vous ne l'utiliseriez pas dans le code de production.
sleske
Certes, il m'a juste fallu un certain temps pour comprendre ce qui se passait lorsque j'ai regardé la sortie pour la première fois.
Ilmari Karonen
6

Je suis à peu près sûr, la JVM s'assurera absolument qu'elle a au moins assez de mémoire pour lever une exception avant de manquer de mémoire.

Oscar Gomez
la source
1
Mais ils frapperaient cette situation, peu importe la quantité de mémoire réservée: stackoverflow.com/questions/9261705/…
Pacerier
4

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

KeithS
la source
"la JVM aura alloué suffisamment de mémoire dans ses propres limites de processus pour créer ladite exception" mais si votre code conserve une référence à cette exception, donc il ne peut pas être réutilisé, comment peut-il en créer une autre? Ou suggérez-vous qu'il a un objet OOME Singleton spécial?
Raedwald
Je ne dis pas cela. Si votre programme intercepte et s'accroche à toutes les exceptions qui ont jamais été créées, y compris celles créées et injectées par la JVM ou le système d'exploitation, la JVM elle-même finira par dépasser les limites définies par le système d'exploitation et le système d'exploitation l'arrêtera pour un GPF ou erreur similaire. Cependant, c'est une mauvaise conception en premier lieu; les exceptions doivent être gérées puis hors de portée, ou être supprimées. Et vous ne devez JAMAIS essayer d'attraper et de continuer sur un SOE ou OOME; autre que «nettoyer» pour que vous puissiez quitter sans problème, vous ne pouvez rien faire pour continuer l'exécution dans ces situations.
KeithS
"c'est un mauvais design en premier lieu": un design terrible. Mais sur le plan pédagogique, la JVM serait-elle conforme à la spécification si elle échouait de cette manière?
Raedwald
4

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

Lectures complémentaires:

Et pour finir , essayer d' attraper une java.lang.Error (et ses classes descendantes) afin d'imprimer une trace de pile peut ne pas vous donner d'informations utiles. Vous voulez un vidage de tas à la place.

Michael Tiffany
la source
4

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

private static final OutOfMemoryError OOME = new OutOfMemoryError();

Plus tard, la JVM exécute l'un des bytecodes Java suivants: «nouveau», «anewarray» ou «multianewarray». Cette instruction oblige la JVM à exécuter un certain nombre d'étapes dans un état de mémoire insuffisante:

  1. Appelez une fonction native, par exemple allocate(). allocate()tente d'allouer de la mémoire pour certains une nouvelle instance d'une classe ou d'un tableau particulier.
  2. Cette demande d'allocation échoue, de sorte que la JVM appelle une autre fonction native, par exemple doGC(), qui tente de faire le garbage collection.
  3. Lorsque cette fonction revient, allocate()essaie à nouveau d'allouer de la mémoire à l'instance.
  4. Si cela échoue (*), la machine throw OOME;virtuelle Java, dans allocate (), fait simplement un , se référant à l'OOME qu'elle a instanciée au démarrage. Notez qu'il n'a pas eu à allouer cet OOME, il y fait simplement référence.

Évidemment, ce ne sont pas des étapes littérales; ils varieront de JVM à JVM dans l'implémentation, mais c'est l'idée de haut niveau.

(*) Une quantité importante de travail se produit ici avant d'échouer. La JVM tentera d'effacer les objets SoftReference, tentera l'allocation directement dans la génération sécurisée lors de l'utilisation d'un collecteur générationnel, et éventuellement d'autres choses, comme la finalisation.

ahawtho
la source
3

Les réponses qui disent que la JVM pré-allouera OutOfMemoryErrorssont en effet correctes.
En plus de tester cela en provoquant une situation de mémoire insuffisante, nous pouvons simplement vérifier le tas de n'importe quelle machine virtuelle Java (j'ai utilisé un petit programme qui fait juste une mise en veille, en 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 java.lang.OutOfMemoryError
170: 1 32 [Ljava.lang.OutOfMemoryError;

On peut alors générer un vidage de tas:

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

et ouvrez-le à l'aide d' Eclipse Memory Analyzer , où une requête OQL montre que la JVM semble réellement pré-allouer OutOfMemoryErrorspour tous les messages possibles:

entrez la description de l'image ici

Le code de la machine virtuelle Java 8 Hotspot qui les préalloue en fait peut être trouvé 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 montre que la JVM essaiera d'abord d'utiliser l'une des erreurs pré-allouées avec de l'espace pour une trace de pile, puis retombera sur une 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;
  }
}
Johan Kaving
la source
Bon article, je vais accepter cela comme réponse pendant une semaine pour lui donner plus de visibilité avant de revenir à la réponse précédente.
Pacerier
Dans Java 8 et versions ultérieures, l'espace de génération permanent a été complètement supprimé et vous ne pouvez plus spécifier la taille de la mémoire d'allocation d'espace de tas à partir de Java 8 et versions ultérieures, car elles ont introduit la gestion de la mémoire de métadonnées de classe dynamique appelée Metaspace. Ce serait bien si vous pouviez montrer un morceau de code qui s'adresse également à PermGen et le comparer avec Metaspace également.
Buhake Sindi
@BuhakeSindi - Je ne vois pas ce que la génération permanente a à voir avec cela. Les nouveaux objets ne sont pas alloués dans la génération permanente comme vous l'indiquez dans votre réponse. Vous ne mentionnez également jamais le fait que OutOfMemoryErrors sont pré-alloués (ce qui est la réponse réelle à la question).
Johan Kaving
1
Ok, ce que je dis, c'est qu'à partir de Java 8, l'allocation des objets est calculée dynamiquement alors qu'avant, elle était allouée sur un espace de tas prédéfini, donc peut-être qu'elle est pré-allouée au préalable. Même si OOME est pré-alloué, un "calcul" est effectué pour déterminer si l'OOME doit être levé (d'où la raison pour laquelle je mets la référence à la spécification JLS).
Buhake Sindi
1
La taille du tas Java est tout aussi prédéfinie dans Java 8 qu'auparavant. La génération permanente faisait partie du tas qui contenait des métadonnées de classe, des chaînes internes et des statiques de classe. Il avait une taille limitée qui devait être réglée séparément de la taille totale du tas. Dans Java 8, les métadonnées de classe ont été déplacées vers la mémoire native et les chaînes internes et les statiques de classe ont été déplacées vers le tas Java standard (voir par exemple ici: infoq.com/articles/Java-PERMGEN-Removed ).
Johan Kaving