Cogiendo java.lang.OutOfMemoryError?


La documentación para java.lang.Error dice:

Un error es una subclase de Lanzable que indica problemas graves que una aplicación razonable no debería tratar de atrapar

Pero como java.lang.Error es una subclase de java.lang.Throwable, puedo tomar este tipo de Arrojadiza.

Entiendo por qué no es buena idea atrapar este tipo de excepción. Por lo que entiendo, si decidimos atraparlo, el controlador de captura no debe asignar ninguna memoria por sí mismo. De lo contrario OutOfMemoryError será lanzado de nuevo.

Entonces, mi pregunta es:

  1. ¿Hay escenarios de palabras reales cuando atrapar java.lang.OutOfMemoryError puede ser una buena idea?
  2. Si decidimos catch java.lang.OutOfMemoryError, ¿cómo podemos estar seguros de que catch handler no asigna ninguna memoria por sí mismo (ninguna herramienta o mejor práctica)?
Author: Michael Petrotta, 2010-04-21

14 answers

Estoy de acuerdo y en desacuerdo con la mayoría de las respuestas aquí.

Hay una serie de escenarios en los que es posible que desee coger un OutOfMemoryError y en mi experiencia (en Windows y Solaris JVM), solo muy rara vez es OutOfMemoryError el toque de muerte a una JVM.

Solo hay una buena razón para capturar un OutOfMemoryError y es cerrar con gracia, liberando recursos limpiamente y registrando la razón del error lo mejor que pueda (si aún es posible hacerlo).

En general, el OutOfMemoryError se produce debido a una asignación de memoria de bloque que no se puede satisfacer con los recursos restantes del montón.

Cuando se lanza el Error el montón contiene la misma cantidad de objetos asignados que antes de la asignación fallida y ahora es el momento de soltar referencias a objetos en tiempo de ejecución para liberar aún más memoria que puede ser necesaria para la limpieza. En estos casos, incluso puede ser posible continuar, pero eso definitivamente sería una mala idea, ya que nunca puede estar 100% seguro de que el JVM está en un estado reparable.

Demostración de que OutOfMemoryError no significa que la JVM esté fuera de memoria en el bloque catch:

private static final int MEGABYTE = (1024*1024);
public static void runOutOfMemory() {
    MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
    for (int i=1; i <= 100; i++) {
        try {
            byte[] bytes = new byte[MEGABYTE*500];
        } catch (Exception e) {
            e.printStackTrace();
        } catch (OutOfMemoryError e) {
            MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
            long maxMemory = heapUsage.getMax() / MEGABYTE;
            long usedMemory = heapUsage.getUsed() / MEGABYTE;
            System.out.println(i+ " : Memory Use :" + usedMemory + "M/" + maxMemory + "M");
        }
    }
}

Salida de este código:

1 : Memory Use :0M/247M
..
..
..
98 : Memory Use :0M/247M
99 : Memory Use :0M/247M
100 : Memory Use :0M/247M

Si se ejecuta algo crítico, normalmente cojo el Error, lo registro en syserr, luego lo registro usando mi marco de registro de elección, luego procedo a liberar recursos y cierro de manera limpia. ¿Qué es lo peor que puede pasar? La JVM está muriendo (o ya está muerta) de todos modos y por la captura de la Error hay al menos una oportunidad de limpieza.

La advertencia es que debe apuntar a la captura de este tipo de errores solo en lugares donde la limpieza es posible. No cobijas catch(Throwable t) {} en todas partes o tonterías como esa.

 66
Author: Chris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-26 18:36:53

Usted puede recuperarse de ello:

package com.stackoverflow.q2679330;

public class Test {

    public static void main(String... args) {
        int size = Integer.MAX_VALUE;
        int factor = 10;

        while (true) {
            try {
                System.out.println("Trying to allocate " + size + " bytes");
                byte[] bytes = new byte[size];
                System.out.println("Succeed!");
                break;
            } catch (OutOfMemoryError e) {
                System.out.println("OOME .. Trying again with 10x less");
                size /= factor;
            }
        }
    }

}

Pero, ¿tiene sentido? ¿Qué más te gustaría hacer? ¿Por qué inicialmente asignarías tanta memoria? Es menos memoria también está bien? ¿Por qué no lo usas ya de todos modos? O si eso no es posible, ¿por qué no darle a la JVM más memoria desde el principio?

Volver a sus preguntas:

1: ¿hay escenarios de palabras reales al capturar Java?lang.OutOfMemoryError puede ser una buena idea?

Ninguno viene a la mente.

2: si cogemos java.lang.OutOfMemoryError ¿cómo podemos estar seguros de que catch handler no asigna ninguna memoria por sí mismo (ninguna herramienta o mejor práctica)?

Depende de lo que ha causado el OOME. Si se declara fuera del bloque try y sucedió paso a paso, entonces sus posibilidades son pocas. Usted puede querer reservar algo de espacio de memoria de antemano:

private static byte[] reserve = new byte[1024 * 1024]; // Reserves 1MB.

Y luego ponerlo a cero durante OOME:

} catch (OutOfMemoryException e) {
     reserve = new byte[0];
     // Ha! 1MB free!
}

Por supuesto, esto no tiene ningún sentido ;) Solo dale a JVM suficiente memoria como tu solicitud requiera. Ejecute un generador de perfiles si es necesario.

 27
Author: BalusC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-04-21 01:03:05

En general, es una mala idea tratar de atrapar y recuperarse de un OOM.

  1. Un OOME también podría haber sido lanzado en otros subprocesos, incluidos subprocesos que su aplicación ni siquiera conoce. Cualquiera de estos hilos ahora estará muerto, y cualquier cosa que estaba esperando una notificación podría quedarse atascada para siempre. En resumen, su aplicación podría estar rota terminalmente.

  2. Incluso si se recupera con éxito, su JVM todavía puede estar sufriendo de hambre en montón y su la aplicación funcionará abismalmente como resultado.

Lo mejor que se puede hacer con un OOME es dejar que la JVM muera.

(Esto supone que la JVM hace morir. Por ejemplo, los OOMs en un hilo de servlet Tomcat no matan a la JVM, y esto lleva al Tomcat a entrar en un estado catatónico donde no responderá a ninguna solicitud ... ni siquiera solicitudes para reiniciar.)

EDITAR

No estoy diciendo que sea una mala idea atrapar a OOM en absoluto. El los problemas surgen cuando luego intentas recuperarte del OOME, ya sea deliberadamente o por descuido. Cada vez que se detecta un OOM (directamente, o como un subtipo de Error o Lanzable) debe volver a desarrollarlo, o organizar que la aplicación / JVM sale.

Aparte: Esto sugiere que para una máxima robustez frente a OOMs una aplicación debe usar Hilo.setDefaultUncaughtExceptionHandler () para establecer un controlador que hará que la aplicación salga en el caso de un OOME, no importa en qué hilo se lanza el OOME. Estaría interesado en opiniones sobre esto ...

El único otro escenario es cuando sabes con seguridad que el OOM no ha resultado en ningún daño colateral; es decir, sabes:

  • lo que causó específicamente el OOME,
  • lo que la aplicación estaba haciendo en ese momento, y que está bien simplemente descartar ese cálculo, y
  • que un OOME (aproximadamente) simultáneo no puede haber ocurrido en otro hilo.

Hay aplicaciones donde es posible saber estas cosas, pero para la mayoría de las aplicaciones no se puede saber con seguridad que la continuación después de un OOME es segura. Incluso si empíricamente "funciona" cuando lo intentas.

(El problema es que se requiere una prueba formal para demostrar que las consecuencias de los OOMEs "anticipados" son seguras, y que los OOMES "no anticipados" no pueden ocurrir dentro del control de un OOME de intento/captura.)

 14
Author: Stephen C,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-26 00:56:42

Sí, hay escenarios del mundo real. Aquí está el mío: Necesito procesar conjuntos de datos de muchos elementos en un clúster con memoria limitada por nodo. Una instancia de JVM dada pasa por muchos elementos uno tras otro, pero algunos de los elementos son demasiado grandes para procesarlos en el clúster: puedo capturar OutOfMemoryError y tomar nota de qué elementos son demasiado grandes. Más tarde, puedo volver a ejecutar solo los elementos grandes en una computadora con más RAM.

(Debido a que es una sola asignación multi-gigabyte de una matriz que falla, la JVM todavía está bien después de detectar el error y hay suficiente memoria para procesar los otros elementos.)

 11
Author: Michael Kuhn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-20 09:43:12

Definitivamente hay escenarios donde atrapar a un OOME tiene sentido. IDEA los captura y abre un cuadro de diálogo que le permite cambiar la configuración de la memoria de inicio (y luego sale cuando haya terminado). Un servidor de aplicaciones podría detectarlos e informarlos. La clave para hacer esto es hacerlo a un alto nivel en el despacho para que tenga una oportunidad razonable de tener un montón de recursos liberados en el punto donde está atrapando la excepción.

Además del escenario IDEA anterior, en general el la captura debe ser de Lanzable, no solo OOM específicamente, y debe hacerse en un contexto donde al menos el hilo se terminará en breve.

Por supuesto, la mayoría de las veces la memoria se muere de hambre y la situación no es recuperable, pero hay maneras en que tiene sentido.

 8
Author: Yishai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-04-21 02:22:00

Me encontré con esta pregunta porque me preguntaba si es una buena idea para coger OutOfMemoryError en mi caso. Estoy respondiendo aquí parcialmente para mostrar otro ejemplo más cuando la captura de este error puede tener sentido para alguien (es decir, yo) y parcialmente para averiguar si es una buena idea en mi caso de hecho (siendo yo un desarrollador uber junior nunca puedo estar demasiado seguro de cualquier línea de código que escribo).

De todos modos, estoy trabajando en una aplicación para Android que se puede ejecutar en diferentes dispositivos con diferentes tamaños de memoria. La parte peligrosa es decodificar un mapa de bits de un archivo y desplazarlo en una instancia de ImageView. No quiero restringir los dispositivos más potentes en términos de tamaño de mapa de bits decodificado, ni puedo estar seguro de que la aplicación no se ejecutará en algún dispositivo antiguo que nunca he encontrado con muy poca memoria. Por lo tanto hago esto:

BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); 
bitmapOptions.inSampleSize = 1;
boolean imageSet = false;
while (!imageSet) {
  try {
    image = BitmapFactory.decodeFile(filePath, bitmapOptions);
    imageView.setImageBitmap(image); 
    imageSet = true;
  }
  catch (OutOfMemoryError e) {
    bitmapOptions.inSampleSize *= 2;
  }
}

De esta manera me las arreglo para proporcionar dispositivos más y menos potentes de acuerdo con sus, o más bien las necesidades de sus usuarios y expectativas.

 7
Author: Maria,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-02-28 11:50:30

Sí, la verdadera pregunta es "¿qué vas a hacer en el controlador de excepciones?"Para casi cualquier cosa útil, asignarás más memoria. Si desea realizar algún trabajo de diagnóstico cuando se produce un error OutOfMemoryError, puede usar el gancho -XX:OnOutOfMemoryError=<cmd> suministrado por la máquina virtual HotSpot. Ejecutará tus comandos cuando ocurra un OutOfMemoryError, y puedes hacer algo útil fuera del montón de Java. Realmente quieres evitar que la aplicación se quede sin memoria en primer lugar, así que averiguar por qué sucede es el primer paso. A continuación, puede aumentar el tamaño del montón del MaxPermSize según corresponda. Aquí hay algunos otros ganchos de HotSpot útiles:

-XX:+PrintCommandLineFlags
-XX:+PrintConcurrentLocks
-XX:+PrintClassHistogram

Ver la lista completa aquí

 5
Author: Rob Heiser,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-04-21 00:41:53

Tengo una aplicación que necesita recuperarse de fallos de OutOfMemoryError, y en programas de un solo subproceso siempre funciona, pero a veces no lo hace en programas multiproceso. La aplicación es una herramienta de prueba Java automatizada que ejecuta secuencias de prueba generadas a la máxima profundidad posible en clases de prueba. Ahora, la interfaz de usuario debe ser estable, pero el motor de prueba puede quedarse sin memoria mientras crece el árbol de casos de prueba. Manejo esto por el siguiente tipo de lenguaje de código en la prueba motor:

boolean isOutOfMemory = false;  // flag used for reporting
try {
   SomeType largeVar;
   // Main loop that allocates more and more to largeVar
   // may terminate OK, or raise OutOfMemoryError
}
catch (OutOfMemoryError ex) {
   // largeVar is now out of scope, so is garbage
   System.gc();                // clean up largeVar data
   isOutOfMemory = true;       // flag available for use
}
// program tests flag to report recovery

Esto funciona siempre en aplicaciones de un solo subproceso. Pero recientemente puse mi motor de prueba en un hilo de trabajo separado de la interfaz de usuario. Ahora, el fuera de la memoria puede ocurrir arbitrariamente en cualquiera de los hilos, y no está claro para mí cómo atraparlo.

Por ejemplo, hice que ocurriera el OOME mientras los fotogramas de un GIF animado en mi interfaz de usuario estaban siendo ciclados por un hilo propietario que es creado detrás de escena por una clase de Swing que está fuera de mi control. Yo había pensado que yo había asignado todos los recursos necesarios de antemano, pero claramente el animador está asignando memoria cada vez que obtiene la siguiente imagen. Si alguien tiene una idea sobre cómo manejar OOMEs criados en cualquier hilo, me encantaría escuchar.

 4
Author: Tony Simons,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-31 09:35:07

La única razón por la que puedo pensar en por qué la captura de errores de OOM podría ser que tiene algunas estructuras de datos masivas que ya no está utilizando, y puede establecer en null y liberar algo de memoria. Pero (1) eso significa que estás desperdiciando memoria, y deberías arreglar tu código en lugar de simplemente cojear después de un OOME, y (2) incluso si lo atraparas, ¿qué harías? OOM puede suceder en cualquier momento, potencialmente dejando todo a medio hacer.

 2
Author: cHao,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-04-21 00:10:08

Para la pregunta 2 ya veo la solución que sugeriría, por BalusC.

  1. ¿Hay algún escenario de palabras reales al capturar java?lang.OutOfMemoryError puede ser una buena idea?

Creo que acabo de encontrar un buen ejemplo. Cuando la aplicación awt está enviando mensajes, el OutOfMemoryError no detectado se muestra en stderr y se detiene el procesamiento del mensaje actual. Pero la aplicación sigue funcionando! El usuario aún puede emitir otros comandos conscientes de los graves problemas que está sucediendo detrás de la escena. Especialmente cuando no puede o no observa el error estándar. Por lo tanto, capturar la excepción oom y proporcionar (o al menos sugerir) el reinicio de la aplicación es algo deseado.

 2
Author: Jarekczek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-25 06:50:11

OOME puede ser atrapado, pero va a ser generalmente inútil, dependiendo de si la JVM es capaz de recoger basura algunos objetos cuando se alcanza la captura, y la cantidad de memoria de montón que queda en ese momento.

Ejemplo: en mi JVM, este programa se ejecuta hasta su finalización:

import java.util.LinkedList;
import java.util.List;

public class OOMErrorTest {             
    public static void main(String[] args) {
        List<Long> ll = new LinkedList<Long>();

        try {
            long l = 0;
            while(true){
                ll.add(new Long(l++));
            }
        } catch(OutOfMemoryError oome){         
            System.out.println("Error catched!!");
        }
        System.out.println("Test finished");
    }  
}

Sin embargo, simplemente agregando una sola línea en la captura le mostrará de qué estoy hablando:

import java.util.LinkedList;
import java.util.List;

public class OOMErrorTest {             
    public static void main(String[] args) {
        List<Long> ll = new LinkedList<Long>();

        try {
            long l = 0;
            while(true){
                ll.add(new Long(l++));
            }
        } catch(OutOfMemoryError oome){         
            System.out.println("Error catched!!");
            System.out.println("size:" +ll.size());
        }
        System.out.println("Test finished");
    }
}

El primer programa funciona bien porque cuando se alcanza la captura, la JVM detecta que la lista no va a ser utilizado más (Esta detección puede ser también una optimización hecha en tiempo de compilación). Así que cuando llegamos a la declaración de impresión, la memoria del montón se ha liberado casi por completo, por lo que ahora tenemos un amplio margen de maniobra para continuar. Este es el mejor caso.

Sin embargo, si el código está organizado como la lista ll se utiliza después de que el OOME ha sido capturado, la JVM no puede recopilarlo. Esto sucede en el segundo fragmento. El OOME, desencadenado por una nueva y larga creación, es atrapado, pero pronto estamos creando un nuevo Objeto (una cadena en la línea System.out,println), y el montón está casi lleno, por lo que se lanza un nuevo OOME. Este es el peor de los casos: intentamos crear un nuevo objeto, fallamos, atrapamos el OOME, sí, pero ahora la primera instrucción que requiere nueva memoria de montón (por ejemplo: crear un nuevo objeto) lanzará un nuevo OOME. Piénsalo, ¿qué más podemos hacer en este punto con tan poca memoria?. Probablemente saliendo. De ahí lo inútil.

Entre las razones por las que la JVM no recoge recursos, uno es realmente aterrador: un recurso compartido con otros hilos también haciendo uso de él. Cualquier persona con cerebro puede ver lo peligroso que puede ser atrapar a OOME si se inserta en alguna aplicación no experimental de cualquier tipo.

Estoy usando un Windows x86 32bits JVM (JRE6). La memoria predeterminada para cada aplicación Java es de 64 MB.

 2
Author: Mister Smith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-19 07:28:28

Solo tengo un escenario donde la captura de un OutOfMemoryError parece tener sentido y parece funcionar.

Escenario: en una aplicación de Android, quiero mostrar varios mapas de bits en la resolución más alta posible, y quiero ser capaz de hacer zoom con fluidez.

Debido al zoom fluido, quiero tener los mapas de bits en memoria. Sin embargo, Android tiene limitaciones en la memoria que dependen del dispositivo y que son difíciles de controlar.

En esta situación, puede haber OutOfMemoryError mientras leyendo el mapa de bits. Aquí, ayuda si lo cojo y luego continúo con una resolución más baja.

 1
Author: Jörg Eisfeld,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-30 19:43:25

Puede capturar cualquier cosa bajo Lanzable, en términos generales, solo debe capturar subclases de Excepción excluyendo RuntimeException (aunque una gran parte de los desarrolladores también capturan RuntimeException... pero esa nunca fue la intención de los diseñadores del lenguaje).

Si se va a coger OutOfMemoryError ¿qué haría usted? La VM está sin memoria, básicamente todo lo que puede hacer es salir. Es probable que ni siquiera pueda abrir un cuadro de diálogo para decirles que está fuera de la memoria, ya que eso sería tome la memoria: -)

La máquina virtual arroja un error OutOfMemoryError cuando realmente está fuera de memoria (de hecho, todos los errores deben indicar situaciones irrecuperables) y realmente no debe haber nada que pueda hacer para lidiar con él.

Las cosas que debe hacer son averiguar por qué se está quedando sin memoria (use un generador de perfiles, como el de NetBeans) y asegúrese de que no tiene fugas de memoria. Si no tiene fugas de memoria, aumente la memoria que asigna a la VM.

 0
Author: TofuBeer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-04-20 23:24:43
  1. Depende de cómo se defina "bueno". Lo hacemos en nuestra aplicación web con errores y funciona la mayor parte del tiempo (afortunadamente, ahora OutOfMemory no sucede debido a una corrección no relacionada). Sin embargo, incluso si lo atrapa, todavía podría haber roto algún código importante: si tiene varios hilos, la asignación de memoria puede fallar en cualquiera de ellos. Por lo tanto, dependiendo de su aplicación todavía hay 10 chance 90% de probabilidad de que se rompa irreversiblemente.
  2. Por lo que entiendo, pila pesada distraerse en el camino invalidará tantas referencias y por lo tanto libre de tanta memoria no deberías preocuparte de eso.

EDITAR: Te sugiero que lo pruebes. Digamos, escribir un programa que recursivamente llama a una función que asigna progresivamente más memoria. Captura OutOfMemoryError y mira si puedes continuar significativamente desde ese punto. De acuerdo con mi experiencia, usted será capaz de, aunque en mi caso sucedió bajo el servidor WebLogic, por lo que podría haber habido algo de magia negra involucrada.

 0
Author: doublep,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-04-20 23:30:09