java - Manejo de error de desbordamiento de pila en el bloque final
recursion jvm (5)
Pero, ¿cómo se está ejecutando la función recursiva en el bloque finally ya que enfrentamos un StackOverflowError?
Si intenta manejar un StackOverflowError
, solo dará como resultado StackOverflowErrors posteriores, ya que está tratando de realizar más ejecuciones en una pila ya completa. No intente atrapar, use esta información para estructurar mejor su código. Este es el punto de una excepción sin marcar en java. Si no está familiarizado con los diferentes tipos de excepciones ...
Excepciones controladas
Las excepciones comprobadas son excepciones que se verifican en tiempo de compilación y representan las condiciones que deben manejarse (a través de una declaración try-catch) ya que está fuera del control del programa. Por ejemplo, si desea llamar a Thread.sleep()
cualquier lugar dentro de un hilo determinado, se le pedirá que maneje una posible InterruptedException
que podría ocurrir si el hilo es interrumpido por un hilo diferente. Dado que se conoce la posibilidad de que esto ocurra en el momento de la compilación, usted como programador es requerido para manejar este caso.
Excepciones no verificadas
La clase java.lang.Throwable dice ...
A los efectos de la verificación de excepciones en tiempo de compilación, Throwable y cualquier subclase de Throwable que no sea también una subclase de RuntimeException o Error se consideran excepciones comprobadas.
Como StackOverflowError
es una subclase de Error
, no se considera una excepción comprobada y, por lo tanto, está "desmarcada". Las excepciones no revisadas a menudo surgen debido a errores de programación, por ejemplo, en su caso, la terminación incorrecta de una recursión infinita . Podrían surgir otras excepciones no comprobadas si intenta acceder a algún miembro de una variable nula o un índice no válido en una matriz. Estas excepciones son casi imposibles de verificar en tiempo de compilación y se usan con más frecuencia para mostrarle al programador un error en su código.
Otras lecturas
Tengo un programa en Java, que se ejecuta infinitas veces.
Código de programa:
void asd()
{
try
{
//inside try block
System.out.println("Inside try !!!");
asd();
}
finally
{
//inside finally
System.out.println("Inside finally !!!");
asd();
}
}
SALIDA: este programa se ejecuta infinitamente, imprimiendo constantemente ambos sysouts.
Mi pregunta: en algún momento, comienza a arrojar StackOverflowErrors desde el bloque try y llega al bloque finally, donde llamamos nuevamente a esta función recursivamente. Pero, ¿cómo se está ejecutando la función recursiva en el bloque finally ya que enfrentamos un StackOverflowError?
¿Cómo maneja la JVM esta situación? ¿Ocurrirá el mismo comportamiento si obtenemos OutOfMemoryErrors también?
El problema es que tu ejemplo de programa es patológico. No funcionará, y no puede funcionar.
Pero ¿cómo se ejecuta la función recursiva en el bloque finally ya que enfrentamos
Error
?
Hay una secuencia bastante complicada de llamadas en curso. Supongamos que la pila puede contener 3 cuadros. Una "ejecución manual" nos da una secuencia de llamadas / instantáneas de pila de llamadas de la siguiente manera:
asd()
asd() > try
asd() > try > asd()
asd() > try > asd() > try
asd() > try > asd() > try > asd() // !
asd() > try > asd() > finally
asd() > try > asd() > finally > asd() // !
asd() > finally
asd() > finally > asd()
asd() > finally > asd() > try
asd() > finally > asd() > try > asd() // !
asd() > finally > asd() > finally
asd() > finally > asd() > finally > asd() // !
END
Como puede ver con una pila de profundidad 3, hicimos 7 llamadas, 4 de las cuales fallaron con un desbordamiento de la pila. Si realiza la ejecución manual para una pila de profundidad 4, obtendrá 15 llamadas, 5 => 31. El patrón es N => 2**N - 1 calls
.
En su caso, la pila predeterminada podrá acomodar cientos o incluso miles de llamadas recursivas.
Diga N = 100. 2 ** 100 es una gran cantidad de llamadas. No es infinito, pero probablemente estarás muerto antes de que el programa termine.
¿Cómo maneja la JVM esta situación?
Como anteriormente. La JVM no está haciendo nada especial. El comportamiento del "bucle infinito efectivo" se debe exclusivamente a la forma en que se escribe su programa.
¿Ocurrirá el mismo comportamiento si obtenemos
OutOfMemoryError
s también?
Erm ... depende de tu programa. Pero estoy seguro de que podría inventar un programa de ejemplo que exhibió un patrón de comportamiento similar.
Obtendrá el mismo error cuando llame a asd
en el bloque finally - necesita dejar que los recuadros asd
en la pila vuelvan / aparezcan para resolver el error
Supongamos que el programa está ejecutando el método asd()
y el espacio de la pila está a punto de terminar. Supongamos también que, en lugar de "inside try" y "inside finally", el método imprime un contador que indica qué tan lejos está la pila:
void asd(int i){
try{
//inside try block
System.out.print(i);
System.out.println("t");
asd(i+1);
}
finally{
//inside finally
System.out.print(i);
System.out.println("f");
asd(i+1);
}
}
}
Ahora esto es lo que hace el programa cuando está a punto de quedarse sin espacio en la pila, cuando i
sea 9154.
La llamada a println("t")
genera los caracteres y luego llama al método println (). Esto hace que el programa se quede sin espacio de pila, por lo que la ejecución se mueve al bloque finally
. La llamada para imprimir allí se queda sin espacio de pila al imprimir la nueva línea. El error se lanza de nuevo, y la ejecución avanza al finally
en el marco de activación por encima de la llamada al método actual. Esto hace que el programa imprima f
por segunda vez, y como hemos sacado un marco de activación de la pila, esta llamada ahora se completa normalmente e imprime una nueva línea. El programa ha dado hasta ahora este resultado:
...
9152t
9153t
9154t9154f9153f // notice we jumped to i=1953
Ahora, el método se llama a sí mismo nuevamente, ahora desde el bloque finally. La situación con el espacio de pila es como antes, por lo que el programa se ejecuta como arriba, excepto que, como estábamos en un bloque finally para la llamada a método de i = 1953, la ejecución del programa termina en el bloque finally de la llamada al método de i = 1952:
9154t9154f9152f
El bloque finally de i = 9152 vuelve a llamar a asd
, pasando i = 9153, y dado que ahora hay suficiente espacio de pila para imprimir una línea completa, el método genera el try-block:
9153t
y luego continúa llamándose a sí mismo, y en esta llamada terminará quedándose sin espacio de pila de nuevo, dando el resultado:
9154t9154f9153f
... y el resto de la salida se puede explicar de manera similar:
9154t9154f9151f
9152t
9153t
9154t9154f9153f
9154t9154f9152f
9153t
9154t9154f9153f
9154t9154f9150f
9151t
9152t
9153t
...
Lo que es importante notar es:
- El bloque finally se ejecuta incluso en el caso de
Error
. - Un programa que se ha topado con un
Error
puede estar en un estado impredecible. Aquí, el único efecto observable es el hecho de queprintln
noprintln
un salto de línea. En un programa más complejo, esto podría significar que no puede confiar en el estado de cualquier cosa en la que el programa haya estado trabajando, y que lo más seguro es rescatar por completo.
Los errores, es decir, OutOfMemoryError, Error, etc. no están destinados a ser manejados. Dejan JVM en estado indefinido, nada está garantizado. En este punto, su aplicación simplemente debe finalizar y debe solucionar el problema que condujo a esto.
Su aplicación no debe tratar de manejar errores O ejecutar después de que uno haya sucedido. Si llama a la función recursiva en este punto, entonces usted es el único culpable. El resultado no es predecible
Consulte "11.1.1. El tipo de excepciones": http://docs.oracle.com/javase/specs/jls/se7/html/jls-11.html