Documentación para java.lang.Error
dice:
Un error es una subclase de Throwable que indica problemas graves que una aplicación razonable no debería intentar detectar.
Pero como java.lang.Error
es una subclase de java.lang.Throwable
, puedo atrapar este tipo de Throwable.
Entiendo por qué no es una buena idea detectar este tipo de excepción. Por lo que tengo entendido, si decidimos capturarlo, el controlador de captura no debería asignar memoria por sí mismo. De OutOfMemoryError
lo contrario , se volverá a lanzar.
Entonces, mi pregunta es:
- ¿Existen escenarios del mundo real en los que la captura
java.lang.OutOfMemoryError
podría ser una buena idea? - Si decidimos capturar
java.lang.OutOfMemoryError
, ¿cómo podemos asegurarnos de que el controlador de captura no asigne memoria por sí mismo (herramientas o mejores prácticas)?
java
try-catch
out-of-memory
Denis Bazhenov
fuente
fuente
Respuestas:
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 captar una
OutOfMemoryError
y, en mi experiencia (en Windows y Solaris JVM), muy pocas veces esOutOfMemoryError
la sentencia de muerte para una JVM.Solo hay una buena razón para detectar un
OutOfMemoryError
error y es cerrar con elegancia, liberar recursos de manera limpia y registrar la razón del error lo mejor que pueda (si aún es posible hacerlo).En general,
OutOfMemoryError
ocurre debido a una asignación de memoria de bloque que no se puede satisfacer con los recursos restantes del montón.Cuando
Error
se lanza, el montón contiene la misma cantidad de objetos asignados que antes de la asignación fallida y ahora es el momento de eliminar las referencias a los 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 definitivamente sería una mala idea ya que nunca puede estar 100% seguro de que la JVM está en un estado reparable.Demostración que
OutOfMemoryError
no significa que la JVM no tenga memoria en el bloque de captura:Salida de este código:
Si ejecuto algo crítico, generalmente lo capturo
Error
, lo registro en syserr, luego lo registro usando el marco de registro de mi elección, luego procedo a liberar recursos y cerrarlo de manera limpia. ¿Qué es lo peor que puede pasar? La JVM está muriendo (o ya está muerta) de todos modos y al detectarlaError
hay al menos una posibilidad de limpieza.La advertencia es que debe apuntar a la detección de este tipo de errores solo en lugares donde la limpieza es posible. No te pongas mantas en
catch(Throwable t) {}
todas partes o tonterías como esa.fuente
OutOfMemoryError
posible que se haya lanzado desde un punto que haya colocado su programa en un estado inconsistente, porque se puede lanzar en cualquier momento. Ver stackoverflow.com/questions/8728866/…Usted puede recuperarse de ella:
¿Pero tiene sentido? ¿Qué más te gustaría hacer? ¿Por qué inicialmente asignaría tanta memoria? ¿También está bien tener menos memoria? ¿Por qué no lo usas de todos modos? O si eso no es posible, ¿por qué no darle más memoria a la JVM desde el principio?
De vuelta a tus preguntas:
Ninguno me viene a la mente.
Depende de lo que haya provocado el OOME. Si se declara fuera del
try
bloque y sucedió paso a paso, entonces sus posibilidades son pocas. Es posible que desee reservar algo de espacio en la memoria de antemano:y luego ajústelo a cero durante OOME:
Por supuesto, esto lo hace todo sin sentido;) Simplemente dé a JVM suficiente memoria según lo requiera su aplicación. Ejecute un generador de perfiles si es necesario.
fuente
null
?En general, es una mala idea intentar detectar y recuperarse de un OOM.
Un OOME también podría haberse lanzado a otros hilos, incluidos los hilos que su aplicación ni siquiera conoce. Cualquiera de estos hilos ahora estará muerto, y cualquier cosa que estuviera esperando una notificación podría quedarse bloqueada para siempre. En resumen, su aplicación podría romperse terminalmente.
Incluso si se recupera con éxito, su JVM aún puede estar sufriendo de inanición y su aplicación funcionará de manera abismal como resultado.
Lo mejor que se puede hacer con un OOME es dejar morir la JVM.
(Esto supone que la JVM hace morir. Por ejemplo, Ooms en un hilo servlet Tomcat no matan a la JVM, y esto lleva a la Tomcat entrar en un estado catatónico en el que no responderá a ninguna solicitud ... ni siquiera pide a reiniciar.)
EDITAR
No estoy diciendo que sea una mala idea detectar OOM en absoluto. Los problemas surgen cuando intenta recuperarse del OOME, ya sea deliberadamente o por descuido. Siempre que detecte un OOM (directamente o como un subtipo de Error o Throwable), debe volver a lanzarlo o hacer que la aplicación / JVM salga.
Aparte: Esto sugiere que para una máxima robustez frente a los OOM, una aplicación debería usar Thread.setDefaultUncaughtExceptionHandler () para establecer un controlador que hará que la aplicación salga en caso de un OOME, sin importar en qué hilo se ejecute el OOME. Me interesaría conocer opiniones sobre esto ...
El único otro escenario es cuando se sabe con certeza que el OOM no ha provocado ningún daño colateral; es decir, sabes:
Hay aplicaciones en las que es posible saber estas cosas, pero para la mayoría de las aplicaciones no puede estar seguro de que la continuación después de un OOME sea segura. Incluso si empíricamente "funciona" cuando lo prueba.
(El problema es que se requiere una prueba formal para demostrar que las consecuencias de los OOME "anticipados" son seguras y que los OOME "no anticipados" no pueden ocurrir dentro del control de un OOME try / catch).
fuente
Error
.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 determinada pasa por muchos elementos uno tras otro, pero algunos de los elementos son demasiado grandes para procesarlos en el clúster: puedo detectar
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 única asignación de varios gigabytes de una matriz que falla, la JVM aún está bien después de detectar el error y hay suficiente memoria para procesar los otros elementos).
fuente
byte[] bytes = new byte[length]
? ¿Por qué no verificarsize
en un punto anterior?size
estará bien con más memoria. Paso por la excepción porque en la mayoría de los casos todo estará bien.Definitivamente hay escenarios en los que atrapar un OOME tiene sentido. IDEA los detecta 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 envío para que tenga una posibilidad razonable de tener una gran cantidad de recursos liberados en el punto en el que está detectando la excepción.
Además del escenario IDEA anterior, en general, la captura debe ser Throwable, no solo OOM específicamente, y debe realizarse en un contexto donde al menos el hilo terminará en breve.
Por supuesto, la mayoría de las veces la memoria se muere de hambre y la situación no se puede recuperar, pero hay formas en que tiene sentido.
fuente
Me encontré con esta pregunta porque me preguntaba si es una buena idea detectar OutOfMemoryError en mi caso. Estoy respondiendo aquí parcialmente para mostrar otro ejemplo más cuando detectar este error puede tener sentido para alguien (es decir, para mí) y parcialmente para averiguar si es una buena idea en mi caso (siendo yo un desarrollador súper junior que nunca puedo estar demasiado seguro de cualquier línea de código que escriba).
De todos modos, estoy trabajando en una aplicación de 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 mostrarlo en una instancia de ImageView. No quiero restringir los dispositivos más potentes en términos del tamaño del mapa de bits decodificado, ni puedo estar seguro de que la aplicación no se ejecutará en un dispositivo antiguo con el que nunca me he encontrado con muy poca memoria. Por eso hago esto:
De esta forma logro proporcionar dispositivos cada vez menos potentes según sus, o más bien las necesidades y expectativas de sus usuarios.
fuente
Sí, la verdadera pregunta es "¿qué vas a hacer en el controlador de excepciones?" Para casi cualquier cosa útil, asignará más memoria. Si desea realizar algún trabajo de diagnóstico cuando se produce un OutOfMemoryError, puede utilizar el enlace
-XX:OnOutOfMemoryError=<cmd>
proporcionado por HotSpot VM. Ejecutará su (s) comando (s) cuando ocurra un OutOfMemoryError, y usted puede hacer algo útil fuera del montón de Java. En primer lugar, realmente desea evitar que la aplicación se quede sin memoria, por lo que averiguar por qué sucede es el primer paso. Luego, puede aumentar el tamaño de pila de MaxPermSize según corresponda. Aquí hay algunos otros ganchos útiles de HotSpot:Vea la lista completa aquí
fuente
OutOfMemeoryError
se puede lanzar un en cualquier punto de su programa (no solo desde unanew
declaración), su programa estará en un estado indefinido cuando capture la excción.Tengo una aplicación que necesita recuperarse de fallas de OutOfMemoryError, y en programas de un solo subproceso siempre funciona, pero a veces no funciona en programas de subprocesos múltiples. La aplicación es una herramienta de prueba Java automatizada que ejecuta secuencias de prueba generadas con la máxima profundidad posible en las 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 con el siguiente tipo de código idiomático en el motor de prueba:
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, la falta de memoria puede ocurrir arbitrariamente en cualquiera de los subprocesos, y no tengo claro cómo detectarla.
Por ejemplo, hice que el OOME ocurriera mientras los fotogramas de un GIF animado en mi interfaz de usuario estaban siendo ciclados por un hilo propietario creado detrás de escena por una clase Swing que está fuera de mi control. Pensé que había asignado todos los recursos necesarios por adelantado, pero claramente el animador está asignando memoria cada vez que busca la siguiente imagen. Si alguien tiene una idea sobre cómo manejar los OOME planteados en cualquier hilo, me encantaría escucharla.
fuente
OOME se puede capturar, pero generalmente será inútil, dependiendo de si la JVM puede recolectar basura en algunos objetos cuando se alcanza la captura, y cuánta memoria de pila queda en ese momento.
Ejemplo: en mi JVM, este programa se ejecuta hasta su finalización:
Sin embargo, simplemente agregando una sola línea en la captura le mostrará de qué estoy hablando:
El primer programa funciona bien porque cuando se alcanza la captura, la JVM detecta que la lista no se va a utilizar más (esta detección también puede ser una optimización realizada en tiempo de compilación). Entonces, 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 de forma tal que la lista
ll
se utiliza después de que se haya capturado el OOME, la JVM no podrá recopilarlo. Esto sucede en el segundo fragmento. El OOME, desencadenado por una nueva creación Long, se detecta, pero pronto estamos creando un nuevo Objeto (una Cadena en laSystem.out,println
línea), 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, capturamos el OOME, sí, pero ahora la primera instrucción que requiere una nueva memoria de pila (por ejemplo: crear un nuevo objeto) arrojará un nuevo OOME. Piénsalo, ¿qué más podemos hacer a estas alturas con tan poca memoria ?. Probablemente solo saliendo. De ahí lo inútil.Entre las razones por las que la JVM no recopila recursos, una es realmente aterradora: un recurso compartido con otros subprocesos que también lo utilizan. Cualquiera con cerebro puede ver lo peligroso que puede ser atrapar OOME si se inserta en alguna aplicación no experimental de cualquier tipo.
Estoy usando una JVM de Windows x86 de 32 bits (JRE6). La memoria predeterminada para cada aplicación Java es de 64 MB.
fuente
ll=null
dentro del bloque de captura?La única razón por la que puedo pensar en por qué detectar errores OOM podría ser que tiene algunas estructuras de datos masivas que ya no está usando, y puede configurarlas en nulo 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 detectaras, ¿qué harías? OOM puede suceder en cualquier momento, potencialmente dejando todo a medio hacer.
fuente
Para la pregunta 2 ya veo la solución que sugeriría, por BalusC.
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 sin darse cuenta de los graves problemas que ocurren detrás de escena. Especialmente cuando no puede o no observa el error estándar. Entonces, capturar la excepción de oom y proporcionar (o al menos sugerir) el reinicio de la aplicación es algo deseado.
fuente
Solo tengo un escenario en el que detectar 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 poder hacer zoom con fluidez.
Debido al zoom fluido, quiero tener los mapas de bits en la memoria. Sin embargo, Android tiene limitaciones de memoria que dependen del dispositivo y son difíciles de controlar.
En esta situación, puede haber OutOfMemoryError al leer el mapa de bits. Aquí, ayuda si lo atrapo y luego continúo con una resolución más baja.
fuente
OutOfMemory
no sucede debido a una solución no relacionada). Sin embargo, incluso si lo detecta, es posible que se haya roto algún código importante: si tiene varios subprocesos, la asignación de memoria puede fallar en cualquiera de ellos. Entonces, dependiendo de su aplicación, todavía hay un 10-90% de posibilidades de que se rompa de manera irreversible.EDITAR: Te sugiero que lo pruebes. Digamos, escriba un programa que llame de forma recursiva a una función que asigne progresivamente más memoria. Capture
OutOfMemoryError
y vea si puede continuar de manera significativa desde ese punto. Según mi experiencia, podrá hacerlo, aunque en mi caso sucedió en el servidor WebLogic, por lo que podría haber algo de magia negra involucrada.fuente
Puede capturar cualquier cosa en Throwable, en términos generales, solo debe capturar subclases de Exception excluyendo RuntimeException (aunque una gran parte de los desarrolladores también detecta RuntimeException ... pero esa nunca fue la intención de los diseñadores del lenguaje).
Si pudieras atrapar OutOfMemoryError, ¿qué demonios harías? La máquina virtual no tiene memoria, básicamente todo lo que puede hacer es salir. Probablemente ni siquiera pueda abrir un cuadro de diálogo para decirles que no tiene memoria, ya que eso ocuparía memoria :-)
La máquina virtual arroja un OutOfMemoryError cuando realmente se queda sin memoria (de hecho, todos los errores deberían indicar situaciones irrecuperables) y realmente no debería haber nada que pueda hacer para solucionarlo.
Lo que debe hacer es averiguar por qué se está quedando sin memoria (use un generador de perfiles, como el de NetBeans) y asegurarse de que no tenga pérdidas de memoria. Si no tiene pérdidas de memoria, aumente la memoria que asigna a la máquina virtual.
fuente