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 null
dado que no hay memoria para new
una 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);
}
}
fuente
OutOfMemoryException
y luego hacer algo que implicó la creación de un buffer de gran tamaño ...OutOfMemoryError
y retuvo una referencia al mismo. Resulta que atrapar unOutOfMemoryError
no 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/…Respuestas:
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:
Para el montón , sección 3.5.3.
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,
OutOfMemoryError
puede ocurrir en diferentes estructuras JVM.fuente
Graham Borland parece tener razón : al menos mi JVM aparentemente reutiliza OutOfMemoryErrors. Para probar esto, escribí un programa de prueba simple:
Ejecutarlo produce esta salida:
Por cierto, la JVM que estoy ejecutando (en Ubuntu 10.04) es esta:
Editar: Traté de ver qué pasaría si obligara a la JVM a quedarse sin memoria usando el siguiente programa:
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
fuente
n
previamente 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?n
marcos en la pila y termina creando y destruyendo marcosn+1
por la eternidad, dando la apariencia de correr sin cesar.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.
fuente
catch
cláusula intenta usar más memoria, la JVM puede seguir lanzando la misma instancia OOM una y otra vez.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.
fuente
De la especificación JVM, Capítulo 3.5.2:
Cada máquina virtual Java tiene que garantizar que arrojará un
OutOfMemoryError
. Eso implica que tiene que ser capaz de crear una instancia deOutOfMemoryError
(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
OutOfMemoryError
sean únicas o creadas bajo demanda. Una JVM podría preparar exactamente una instanciaOutOfMemoryError
durante 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 instanciaOutOfMemoryError
que vemos podría ser un singleton.fuente
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.List
y 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 space
siprintStackTrace()
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 ("[...]").
El programa
fuente
System.out
peroprintStackTrace()
usaSystem.err
de forma predeterminada. Probablemente obtendrá mejores resultados al usar cualquiera de las transmisiones de manera consistente.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.
fuente
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.
fuente
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.
fuente
Para aclarar aún más la respuesta de @Graham Borland, funcionalmente, la JVM hace esto al inicio:
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:
allocate()
.allocate()
intenta asignar memoria para alguna instancia nueva de una clase o matriz particular.doGC()
, que intenta hacer la recolección de basura.allocate()
intenta asignar memoria para la instancia una vez más.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.
fuente
Las respuestas que dicen que la JVM se preasignará
OutOfMemoryErrors
son 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
jmap
vemos que parece haber 9 instancias de OutOfMemoryError (a pesar de que tenemos mucha memoria):Entonces podemos generar un volcado de montón:
y ábralo usando Eclipse Memory Analyzer , donde una consulta OQL muestra que la JVM realmente parece preasignarse
OutOfMemoryErrors
para todos los mensajes posibles: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):
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:
fuente
Metaspace
. Sería bueno si pudieras mostrar un código que también atienda a PermGen y compararlo también con Metaspace.