¿Por qué es posible recuperarse de un StackOverflowError?

100

Me sorprende cómo es posible continuar la ejecución incluso después de que se StackOverflowErrorhaya producido un error en Java.

Sé que StackOverflowErrores una subclase de la clase Error. La clase Error está documentada como "una subclase de Throwable que indica problemas graves que una aplicación razonable no debería intentar detectar".

Esto suena más a una recomendación que a una regla, subtendiendo que detectar un error como un StackOverflowError está permitido y depende de la razonabilidad del programador no hacerlo. Y mira, probé este código y termina normalmente.

public class Test
{
    public static void main(String[] args)
    {
        try {
            foo();
        } catch (StackOverflowError e) {
            bar();
        }
        System.out.println("normal termination");
    }

    private static void foo() {
        System.out.println("foo");
        foo();
    }

    private static void bar() {
        System.out.println("bar");
    }
}

¿Cómo puede ser esto? Creo que para cuando se lance StackOverflowError, la pila debería estar tan llena que no haya espacio para llamar a otra función. ¿El bloque de manejo de errores se está ejecutando en una pila diferente o qué está sucediendo aquí?

usuario3370796
fuente
57
Cometo errores en StackOverflow todo el tiempo. Sin embargo, no me impide volver.
10
Oye ... ¡Escuché que te gustaron los desbordamientos de pila, así que pusimos un desbordamiento de pila en tu stackoverflow.com!
Pierre Henry
Porque las arquitecturas modernas usan Frame Pointers para facilitar el desenrollado de pilas, incluso parciales. Siempre que el código + contexto para hacer eso no tenga que asignarse dinámicamente fuera de la pila, no debería haber ningún problema.
RBarryYoung

Respuestas:

119

Cuando la pila se desborda y StackOverflowErrorse lanza, el manejo habitual de excepciones desenrolla la pila. Desenrollar la pila significa:

  • abortar la ejecución de la función actualmente activa
  • eliminar su marco de pila, continuar con la función de llamada
  • abortar la ejecución de la persona que llama
  • eliminar su marco de pila, continuar con la función de llamada
  • y así...

... hasta que se capture la excepción. Esto es normal (de hecho, necesario) e independiente de qué excepción se lanza y por qué. Dado que detecta la excepción fuera de la primera llamada a foo(), los miles de foomarcos de pila que llenaban la pila se han desenrollado y la mayor parte de la pila está libre para volver a usarse.


fuente
1
@fge Siéntase libre de editar, consideré un salto de párrafo pero no pude encontrar un lugar donde se vea bien.
1
Podrías usar viñetas ... Soy reacio a editar publicaciones de otras personas;)
fge
1
El punto es que el más interno footerminó con un estado indefinido, por lo que cualquier objeto que pueda haber tocado debe asumirse que está roto. Como no sabe en qué función se produjo el desbordamiento de la pila, solo que debe ser un descendiente del trybloque que lo atrapó, cualquier objeto que pueda ser modificado por cualquier método accesible desde allí ahora es sospechoso. Por lo general, no vale la pena averiguar qué sucedió e intentar solucionarlo.
Simon Richter
2
@delnan, creo que la respuesta está incompleta sin entrar también en detalles por qué esto es una mala idea. La diferencia con una excepción lanzada explícitamente es que Errorno se puede anticipar s incluso cuando se escribe código seguro de excepción.
Simon Richter
1
@SimonRichter No, la pregunta es bastante específica. No se trata de manejar Errors. El OP solo pregunta sobre StackOverflowErrorel manejo de este error , y pregunta algo específico : cómo puede no fallar una llamada a un método cuando se detecta este error.
Bakuriu
23

Cuando se lanza StackOverflowError, la pila está llena. Sin embargo, cuando se detecta , todas esas foollamadas se han eliminado de la pila. barse puede ejecutar normalmente porque la pila ya no se desborda con foos. (Tenga en cuenta que no creo que JLS garantice que pueda recuperarse de un desbordamiento de pila como este).

user2357112 es compatible con Monica
fuente
12

Cuando se produce StackOverFlow, la JVM aparecerá en el punto de captura, liberando la pila.

En su ejemplo, se deshace de todos los tontos apilados.

Nicolas Defranoux
fuente
8

Porque la pila en realidad no se desborda. Un mejor nombre podría ser AttemptToOverflowStack. Básicamente, lo que significa es que el último intento de ajustar el marco de la pila es erróneo porque no queda suficiente espacio libre en la pila. La pila podría tener mucho espacio restante, pero no suficiente. Entonces, cualquier operación que hubiera dependido de que la llamada tuviera éxito (típicamente una invocación de método), nunca se ejecuta y todo lo que queda es que el programa se ocupe de ese hecho. Lo que significa que realmente no es diferente de cualquier otra excepción. De hecho, podría detectar la excepción en la función que realiza la llamada.

jmoreno
fuente
1
¡Solo tenga cuidado si hace esto que su controlador de excepciones no requiera más espacio de pila del que está disponible!
Vince
2

Como ya se ha respondido , es posible ejecutar código, y en particular llamar funciones, después de capturar un StackOverflowErrorporque el procedimiento normal de manejo de excepciones de la JVM desenrolla la pila entre throwlos catchpuntos y, liberando espacio de pila para su uso. Y su experimento confirma que ese es el caso.

Sin embargo, eso no es lo mismo que decir que, en general, es posible recuperarse de un StackOverflowError.

A StackOverflowErrorIS-A VirtualMachineError, que ES-AN Error. Como señala, Java proporciona algunos consejos vagos para Error:

Indica problemas graves que una aplicación razonable no debería intentar detectar.

y usted, razonablemente, llega a la conclusión de que deberíaError parecer que atrapar y podría estar bien en algunas circunstancias. Tenga en cuenta que realizar un experimento no demuestra que, en general, algo sea seguro. Solo las reglas del lenguaje Java y las especificaciones de las clases que usa pueden hacer eso. A VirtualMachineErrores una clase especial de excepción, porque la Especificación del lenguaje Java y la Especificación de la máquina virtual Java proporcionan información sobre la semántica de esta excepción. En particular, este último dice :

La implementación de una máquina virtual de Java arroja un objeto que es una instancia de una subclase de la clase VirtualMethodErrorcuando un error interno o una limitación de recursos le impide implementar la semántica descrita en este capítulo. Esta especificación no puede predecir dónde se pueden encontrar errores internos o limitaciones de recursos y no exige con precisión cuándo se pueden informar. Por lo tanto, cualquiera de las VirtualMethodErrorsubclases definidas a continuación puede lanzarse en cualquier momento durante el funcionamiento de la máquina virtual Java:

...

  • StackOverflowError: La implementación de la máquina virtual Java se ha quedado sin espacio de pila para un subproceso, normalmente porque el subproceso está haciendo un número ilimitado de invocaciones recursivas como resultado de una falla en el programa en ejecución.

El problema crucial es que "no se puede predecir" dónde o cuándo se StackOverflowErrorlanzará un . No hay garantías sobre dónde no se lanzará. No puede confiar en que se lance al ingresar a un método, por ejemplo. Podría lanzarse en un punto dentro de un método.

Esta imprevisibilidad es potencialmente desastrosa. Como se puede lanzar dentro de un método, podría lanzarse en parte a través de una secuencia de operaciones que la clase considera una operación "atómica", dejando el objeto en un estado parcialmente modificado, inconsistente. Con el objeto en un estado inconsistente, cualquier intento de usar ese objeto podría resultar en un comportamiento erróneo. En todos los casos prácticos, no puede saber qué objeto se encuentra en un estado inconsistente, por lo que debe asumir que ningún objeto es confiable. Por lo tanto, cualquier operación de recuperación o intento de continuar después de que se detecta la excepción podría tener un comportamiento erróneo. Por lo tanto, lo único seguro que puede hacer es no atraparStackOverflowError, sino más bien para permitir que el programa termine. (En la práctica, puede intentar hacer un registro de errores para ayudar a solucionar problemas, pero no puede confiar en que el registro funcione correctamente). Es decir, no puede recuperarse de forma fiable de unStackOverflowError .

Raedwald
fuente