¿Qué sucede cuando no hay suficiente memoria para lanzar un OutOfMemoryError?

207

Soy consciente de que cada objeto requiere memoria de montón y cada primitiva / referencia en la pila requiere memoria de pila.

Cuando intento crear un objeto en el montón y no hay suficiente memoria para hacerlo, la JVM crea un java.lang.OutOfMemoryError en el montón y me lo arroja.

De manera implícita, esto significa que JVM reserva algo de memoria al inicio.

¿Qué sucede cuando esta memoria reservada se agota (definitivamente se agotaría, lea la discusión a continuación) y la JVM no tiene suficiente memoria en el montón para crear una instancia de java.lang.OutOfMemoryError ?

¿Simplemente se cuelga? ¿O me lanzaría un nulldado que no hay memoria para newuna instancia de 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
}

==

¿Por qué la JVM no puede reservar suficiente memoria?

No importa cuánta memoria esté reservada, aún es posible que esa memoria se use si la JVM no tiene una forma de "reclamar" esa memoria:

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

O más concisamente:

private void OnOOM(java.lang.OutOfMemoryError e) {
    try {
        e.printStackTrace();
    } catch (java.lang.OutOfMemoryError e2) {
        OnOOM(e2);
    }
}
Pacerier
fuente
2
su respuesta dependería en gran medida de
JVM
23
Una biblioteca de telefonía Una vez utilizado (en los 90 's) para la captura de la OutOfMemoryExceptiony luego hacer algo que implicó la creación de un buffer de gran tamaño ...
Tom Hawtin - tackline
@ TomHawtin-tackline ¿Qué pasa si las operaciones involucradas en hacer eso arrojan otro OOM?
Pacerier
38
Al igual que un teléfono celular, se queda sin batería, pero tiene la batería suficiente para mantener el spam "Se está quedando sin batería".
Kazuma
1
"Qué sucede cuando esta memoria reservada se agota": eso podría suceder solo si el programa capturó el primero OutOfMemoryErrory retuvo una referencia al mismo. Resulta que atrapar un OutOfMemoryErrorno es tan útil como uno podría pensar, porque no puede garantizar casi nada sobre el estado de su programa al atraparlo. Ver stackoverflow.com/questions/8728866/…
Raedwald

Respuestas:

145

La JVM nunca se queda realmente sin memoria. Realiza el cálculo de memoria de la pila del montón de antemano.

La Estructura de la JVM, Capítulo 3 , sección 3.5.2 establece:

  • Si las pilas de máquinas virtuales Java se pueden expandir dinámicamente, y se intenta la expansión pero se puede disponer de memoria insuficiente para efectuar la expansión, o si se puede disponer de memoria insuficiente para crear la pila inicial de máquinas virtuales Java para un nuevo subproceso, el virtual Java la máquina lanza un OutOfMemoryError.

Para el montón , sección 3.5.3.

  • Si un cálculo requiere más almacenamiento dinámico del que puede proporcionar el sistema de gestión de almacenamiento automático, la máquina virtual Java arroja un OutOfMemoryError.

Entonces, hace un cálculo por adelantado antes de hacer la asignación del objeto.


Lo que sucede es que la JVM intenta asignar memoria para un objeto en la memoria llamada región de generación permanente (o PermSpace). Si la asignación falla (incluso después de que la JVM invoca al recolector de basura para intentar asignar espacio libre), arroja un OutOfMemoryError. Incluso las excepciones requieren un espacio de memoria, por lo que el error se generará indefinidamente.

Otras lecturas. ? Además, OutOfMemoryErrorpuede ocurrir en diferentes estructuras JVM.

Buhake Sindi
fuente
10
Quiero decir que sí, pero ¿la máquina virtual Java también no necesitaría memoria para lanzar un OutOfMemoryError? ¿Qué sucede cuando no hay memoria para lanzar un OOM?
Pacerier
55
Pero si la JVM no devuelve la referencia a la misma instancia de un OOM, ¿no está de acuerdo en que eventualmente se quedará sin su memoria reservada? (como se demuestra en el código de la pregunta)
Pacerier
1
Permítanme poner una referencia al comentario de Graham aquí: stackoverflow.com/questions/9261705/…
Pacerier
2
Podría ser bueno si el VM retuvo un singleton de Excepción OOM para el caso extremo declarado, y en una planta de energía nuclear.
John K
8
@ John: Espero que las centrales nucleares no estén programadas en Java, al igual que los transbordadores espaciales y los Boeing 757 no están programados en Java.
Dietrich Epp
64

Graham Borland parece tener razón : al menos mi JVM aparentemente reutiliza OutOfMemoryErrors. Para probar esto, escribí un programa de prueba 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);
    }
}

Ejecutarlo produce esta salida:

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

Por cierto, la JVM que estoy ejecutando (en Ubuntu 10.04) es esta:

$ 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)

Editar: Traté de ver qué pasaría si obligara a la JVM a quedarse sin memoria usando el siguiente programa:

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

Como resultado, parece que se repite para siempre. Sin embargo, curiosamente, tratar de terminar el programa con Ctrl+ Cno funciona, solo da el siguiente mensaje:

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
fuente
Buena prueba, lo mismo para mí con la versión "1.7.0_01" Java HotSpot (TM) VM de servidor de 64 bits
Pacerier
Interesante. Eso hace que parezca que la JVM no comprende completamente la recursividad de la cola ... (Como si estuviera haciendo una reutilización de la pila recursiva de la cola, pero no lo suficiente como para limpiar toda la memoria ...)
Izkata
Modifiqué su código para ver cuántos diferentes obtengo; siempre obtengo la rama else ejecutada exactamente 5 veces.
Irfy
@Izkata: Prefiero decir que es una decisión consciente asignar npreviamente OOM y reutilizar uno de ellos después, para que siempre se pueda lanzar un OOM . ¿JVM de Sun / Oracle no admite la recursividad de cola en todo IIRC?
Irfy
10
@Izkata El bucle se está ejecutando aparentemente sin fin porque la JVM está constantemente lanzando un mismo OOM (quinto más o menos) después de que se quede sin memoria. Por lo tanto, tiene nmarcos en la pila y termina creando y destruyendo marcos n+1por la eternidad, dando la apariencia de correr sin cesar.
Irfy
41

La mayoría de los entornos de tiempo de ejecución se preasignarán al inicio o, de lo contrario, reservarán suficiente memoria para lidiar con situaciones de falta de memoria. Me imagino que la mayoría de las implementaciones de JVM sensatas harían esto.

Graham Borland
fuente
1
stackoverflow.com/questions/9261215/… : Verdadero, pero si la JVM reservó 100 unidades de memoria para hacer eso, y gastó 1 unidad en el primer OOM, ¿qué sucede si en mi bloque de captura OOM hago e.printStackTrace ()? e.printStackTrace () también requiere memoria. Luego, la JVM gastaría otra unidad de memoria para arrojarme otra OOM (quedan 98 unidades) y lo atrapo con un e.printStackTrace () para que la JVM me arroje otra OOM (quedan 97 unidades) y eso fue atrapado y quería ..
Pacerier
3
Esto es exactamente por qué OOME nunca solía incluir un seguimiento de pila: ¡los seguimientos de pila ocupan memoria! OOME solo comenzó a intentar incluir un seguimiento de pila en java 6 ( blogs.oracle.com/alanb/entry/… ). Supongo que si el seguimiento de la pila no es posible, la excepción se produce sin un seguimiento de la pila.
Sean Reilly
1
@SeanReilly Quiero decir que una excepción que no tiene un seguimiento de pila sigue siendo un Objeto, que aún requiere memoria. Se necesita memoria independientemente de si se proporciona un seguimiento de pila. ¿Es cierto que en el bloque catch si no queda memoria para crear un OOM (no hay memoria para crear incluso uno sin seguimiento de pila), entonces cogería un valor nulo?
Pacerier
17
La JVM podría devolver múltiples referencias a una única instancia estática de la excepción OOM. Entonces, incluso si su catchcláusula intenta usar más memoria, la JVM puede seguir lanzando la misma instancia OOM una y otra vez.
Graham Borland
1
@TheEliteGentleman Estoy de acuerdo en que esas son muy buenas respuestas también, pero la JVM vive en una máquina física. Esas respuestas no explicaron cómo la JVM podría tener mágicamente suficiente memoria para proporcionar siempre una instancia de OOM. "Siempre es la misma instancia" parece resolver el enigma.
Pacerier
23

La última vez que estuve trabajando en Java y usando un depurador, el inspector del montón mostró que la JVM asignó una instancia de OutOfMemoryError al inicio. En otras palabras, asigna el objeto antes de que su programa tenga la oportunidad de comenzar a consumir, y mucho menos quedarse sin memoria.

benzado
fuente
12

De la especificación JVM, Capítulo 3.5.2:

Si las pilas de máquinas virtuales Java se pueden expandir dinámicamente, y se intenta la expansión pero se puede disponer de memoria insuficiente para efectuar la expansión, o si se puede disponer de memoria insuficiente para crear la pila inicial de máquinas virtuales Java para un nuevo subproceso, el virtual Java la máquina lanza unOutOfMemoryError .

Cada máquina virtual Java tiene que garantizar que arrojará un OutOfMemoryError. Eso implica que tiene que ser capaz de crear una instancia de OutOfMemoryError(o tenerla creada de antemano) incluso si no queda espacio de almacenamiento dinámico.

Aunque no tiene que garantizar, queda suficiente memoria para atraparlo e imprimir un buen seguimiento de pila ...

Adición

Agregó un código para mostrar que la JVM podría quedarse sin espacio de almacenamiento dinámico si tuviera que lanzar más de uno OutOfMemoryError. Pero tal implementación violaría el requisito de arriba.

No es necesario que las instancias lanzadas OutOfMemoryErrorsean únicas o creadas bajo demanda. Una JVM podría preparar exactamente una instancia OutOfMemoryErrordurante el inicio y lanzarla cada vez que se quede sin espacio de almacenamiento dinámico, que es una vez, en un entorno normal. En otras palabras: la instancia OutOfMemoryErrorque vemos podría ser un singleton.

Andreas Dolk
fuente
Supongo que para implementar esto, tendría que abstenerse de registrar el seguimiento de la pila, si el espacio era escaso.
Raedwald
@Raedwald: De hecho, esto es justo lo que hace Oracle VM, vea mi respuesta.
sleske
11

Interesante pregunta :-). Mientras que los otros han dado buenas explicaciones de los aspectos teóricos, decidí probarlo. Esto está en Oracle JDK 1.6.0_26, Windows 7 de 64 bits.

Configuración de prueba

Escribí un programa simple para agotar la memoria (ver más abajo).

El programa solo crea una estática java.util.Listy sigue introduciendo nuevas cadenas en él, hasta que se lanza OOM. Luego lo atrapa y continúa rellenando en un bucle sin fin (pobre JVM ...).

Resultado de la prueba

Como se puede ver en la salida, las primeras cuatro veces que se lanza OOME, viene con un seguimiento de la pila. Después de eso, los OOME posteriores solo se imprimen java.lang.OutOfMemoryError: Java heap spacesi printStackTrace()se invoca.

Entonces, aparentemente, la JVM hace un esfuerzo para imprimir un seguimiento de la pila si puede, pero si la memoria es realmente escasa, simplemente omite el seguimiento, tal como lo sugieren las otras respuestas.

También es interesante el código hash de OOME. Tenga en cuenta que los primeros OOME tienen hashes diferentes. Una vez que la JVM comienza a omitir los rastros de la pila, el hash es siempre el mismo. Esto sugiere que la JVM utilizará instancias OOME nuevas (¿preasignadas?) El mayor tiempo posible, pero si se trata de empujar, simplemente reutilizará la misma instancia en lugar de no tener nada que tirar.

Salida

Nota: trunqué algunos rastros de la pila para facilitar la lectura de la salida ("[...]").

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...
[...]

El programa

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
fuente
Cuando se trata de empujar, no puede asignar memoria para OOME, por lo que elimina los que ya se crearon.
Buhake Sindi
1
Nota menor: parte de su salida parece estar fuera de secuencia, presumiblemente porque está imprimiendo System.outpero printStackTrace()usa System.errde forma predeterminada. Probablemente obtendrá mejores resultados al usar cualquiera de las transmisiones de manera consistente.
Ilmari Karonen
@IlmariKaronen: Sí, me di cuenta de eso. Es solo un ejemplo, por lo que no importó. Obviamente no lo usarías en el código de producción.
sleske
Es cierto, solo me tomó un tiempo descubrir qué estaba pasando la mierda cuando vi por primera vez la salida.
Ilmari Karonen
6

Estoy bastante seguro, la JVM se asegurará absolutamente de que tenga al menos suficiente memoria para lanzar una excepción antes de que se quede sin memoria.

Oscar Gomez
fuente
1
Pero enfrentarían esta situación sin importar cuánta memoria esté reservada: stackoverflow.com/questions/9261705/…
Pacerier
4

Las excepciones que indican un intento de violar los límites de un entorno de memoria administrada son manejadas por el tiempo de ejecución de dicho entorno, en este caso la JVM. La JVM es su propio proceso, que ejecuta la IL de su aplicación. En caso de que un programa intente hacer una llamada que extienda la pila de llamadas más allá de los límites, o asigne más memoria de la que la JVM puede reservar, el tiempo de ejecución inyectará una excepción, lo que hará que la pila de llamadas se desenrolle. Independientemente de la cantidad de memoria que su programa necesite actualmente o de la profundidad de su pila de llamadas, la JVM habrá asignado suficiente memoria dentro de sus propios límites de proceso para crear dicha excepción e inyectarla en su código.

KeithS
fuente
"la JVM habrá asignado suficiente memoria dentro de sus propios límites de proceso para crear dicha excepción" pero si su código retiene una referencia a esa excepción, por lo que no puede reutilizarse, ¿cómo puede crear otra? ¿O estás sugiriendo que tiene un objeto OOME Singleton especial?
Raedwald
No estoy sugiriendo eso. Si su programa atrapa y se aferra a todas las excepciones que alguna vez se hayan creado, incluidas las creadas e inyectadas por la JVM o el SO, entonces, con el tiempo, la propia JVM excederá algún límite establecido por el SO, y el SO lo cerrará por un GPF o error similar Sin embargo, ese es un mal diseño en primer lugar; las excepciones deben ser manejadas y luego salirse del alcance o ser descartadas. Y NUNCA debe intentar atrapar y continuar en un SOE u OOME; Aparte de "limpiar" para que pueda salir con gracia, no hay nada que pueda hacer para continuar la ejecución en esas situaciones.
KeithS
"Eso es mal diseño en primer lugar": diseño terrible. Pero pedagógicamente, ¿ se conformaría la JVM con la especificación si fallara de esa manera?
Raedwald
4

Parece confundir la memoria virtual reservada por la JVM en la que la JVM ejecuta programas Java con la memoria nativa del sistema operativo host en la que la JVM se ejecuta como un proceso nativo. La JVM en su máquina se ejecuta en la memoria administrada por el sistema operativo, no en la memoria que la JVM ha reservado para ejecutar programas Java.

Otras lecturas:

Y como nota final, intentar atrapar un java.lang.Error (y sus clases descendientes) para imprimir un stacktrace puede no brindarle información útil. Desea un volcado de montón en su lugar.

Michael Tiffany
fuente
4

Para aclarar aún más la respuesta de @Graham Borland, funcionalmente, la JVM hace esto al inicio:

private static final OutOfMemoryError OOME = new OutOfMemoryError();

Más tarde, la JVM ejecuta uno de los siguientes códigos de bytes de Java: 'nuevo', 'anewarray' o 'multianewarray'. Esta instrucción hace que la JVM realice una serie de pasos en una condición de falta de memoria:

  1. Invocar una función nativa, por ejemplo allocate(). allocate()intenta asignar memoria para alguna instancia nueva de una clase o matriz particular.
  2. Esa solicitud de asignación falla, por lo que la JVM invoca otra función nativa, por ejemplo doGC(), que intenta hacer la recolección de basura.
  3. Cuando esa función vuelve, allocate()intenta asignar memoria para la instancia una vez más.
  4. Si eso falla (*), entonces la JVM, dentro de allocate (), simplemente hace un throw OOME;, refiriéndose al OOME que instancia en el inicio. Tenga en cuenta que no tuvo que asignar ese OOME, solo se refiere a él.

Obviamente, estos no son pasos literales; variarán de JVM a JVM en la implementación, pero esta es la idea de alto nivel.

(*) Una cantidad significativa de trabajo sucede aquí antes de fallar. La JVM intentará borrar los objetos de SoftReference, intentará asignarlos directamente a la generación permanente cuando utilice un recopilador generacional y posiblemente otras cosas, como la finalización.

ahawtho
fuente
3

Las respuestas que dicen que la JVM se preasignará OutOfMemoryErrorsson correctas.
Además de probar esto al provocar una situación de falta de memoria, simplemente podemos verificar el montón de cualquier JVM (utilicé un pequeño programa que simplemente duerme, ejecutándolo usando HVSpot JVM de Oracle desde la actualización 31 de Java 8).

Con el uso jmapvemos que parece haber 9 instancias de OutOfMemoryError (a pesar de que tenemos mucha memoria):

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

Entonces podemos generar un volcado de montón:

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

y ábralo usando Eclipse Memory Analyzer , donde una consulta OQL muestra que la JVM realmente parece preasignarse OutOfMemoryErrorspara todos los mensajes posibles:

ingrese la descripción de la imagen aquí

El código para la JVM Hotspot de Java 8 que realmente asigna previamente se puede encontrar aquí , y se ve así (con algunas partes omitidas):

...
// 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;
}
...

y este código muestra que la JVM primero intentará usar uno de los errores preasignados con espacio para un seguimiento de pila, y luego volverá a uno sin un seguimiento de pila:

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
fuente
Buena publicación, aceptaré esto como respuesta durante una semana para darle más visibilidad antes de volver a la respuesta anterior.
Pacerier
En Java 8 y versiones posteriores, el espacio de generación permanente se ha eliminado por completo y ya no puede especificar el tamaño de memoria de asignación de espacio de almacenamiento dinámico de Java 8 y versiones posteriores, ya que introdujeron la gestión de memoria de metadatos de clase dinámica llamada Metaspace. Sería bueno si pudieras mostrar un código que también atienda a PermGen y compararlo también con Metaspace.
Buhake Sindi
@BuhakeSindi - No veo qué tiene que ver la Generación Permanente con esto. Los objetos nuevos no se asignan en la Generación Permanente como usted indica en su respuesta. Tampoco menciona el hecho de que OutOfMemoryErrors están asignados previamente (que es la respuesta real a la pregunta).
Johan Kaving
1
Ok, lo que estoy diciendo es que desde Java 8, la asignación de objetos se calcula dinámicamente, mientras que antes se asignaba en un espacio dinámico predefinido, por lo tanto, tal vez se preasigne de antemano. A pesar de que los OOME están asignados previamente, se realiza un "cálculo" para determinar si es necesario lanzar el OOME (de ahí que se haga referencia a la especificación JLS).
Buhake Sindi
1
El tamaño de almacenamiento dinámico de Java está tan predefinido en Java 8 como lo estaba antes. La generación permanente era parte del montón que contenía metadatos de clase, cadenas internas y estadísticas de clase. Tenía un tamaño limitado que debía ajustarse por separado del tamaño de almacenamiento dinámico total. En Java 8, los metadatos de la clase se han movido a la memoria nativa y las cadenas internas y las estadísticas de clase se han movido al montón de Java normal (ver, por ejemplo, aquí: infoq.com/articles/Java-PERMGEN-Removed ).
Johan Kaving