¿Qué causa un error de desbordamiento de stack?

He buscado en todas partes y no puedo encontrar una respuesta sólida. De acuerdo con la documentación, Java arroja un error java.lang.StackOverflowError en las siguientes circunstancias:

Lanzado cuando se produce un desbordamiento de la stack porque una aplicación recurre demasiado profundamente.

Pero esto plantea dos preguntas:

  • ¿No hay otras formas de que ocurra un desbordamiento de stack, no solo a través de la recursión?
  • ¿El StackOverflowError ocurre antes de que la JVM desborde realmente la stack o después?

Para profundizar en la segunda pregunta:

Cuando Java lanza el StackOverflowError, ¿puede suponer con seguridad que la stack no escribió en el montón? Si reduce el tamaño de la stack o del montón en un bash / captura de una función que arroja un desbordamiento de stack, ¿puede seguir trabajando? ¿Está esto documentado en algún lugar?

Respuestas que no estoy buscando:

  • Un StackOverflow sucede debido a una mala recursión.
  • Un StackOverflow ocurre cuando el montón se encuentra con la stack.

Parece que estás pensando que un error de stackflow es como una excepción de desbordamiento de búfer en progtwigs nativos, cuando existe el riesgo de escribir en la memoria que no se haya asignado para el búfer y, por lo tanto, corromper algunas otras ubicaciones de memoria. No es el caso en absoluto.

JVM tiene una memoria asignada asignada para cada stack de cada subproceso, y si ocurre un bash de invocar un método para llenar esta memoria, JVM arroja un error. Justo como lo haría si estuvieras tratando de escribir en el índice N de una matriz de longitud N. No puede haber daños en la memoria. La stack no puede escribir en el montón.

Un StackOverflowError es para la stack lo que OutOfMemoryError es para el montón: simplemente indica que no hay más memoria disponible.

Descripción de los errores de máquinas virtuales (§6.3)

StackOverflowError : la implementación de Java Virtual Machine se ha quedado sin espacio de stack para un hilo, normalmente porque el hilo está realizando un número ilimitado de invocaciones recursivas como resultado de un error en el progtwig en ejecución.

¿No hay otras formas de que ocurra un desbordamiento de stack, no solo a través de la recursión?

Por supuesto. Solo sigue llamando a métodos, sin volver jamás. Sin embargo, necesitará muchos métodos, a menos que permita la recursión. En realidad, no hace una diferencia: un marco de stack es un marco de stack, si es uno de uno recursivo o no es el mismo.

La respuesta a su segunda pregunta es: El stackoverflow se detecta cuando la JVM intenta asignar el marco de stack para la próxima llamada, y encuentra que no es posible. Entonces, nada será sobrescrito.

¿No hay otras formas de que ocurra un desbordamiento de stack, no solo a través de la recursión?

Desafío aceptado 🙂 StackOverflowError sin recursión (desafío fracasado, ver comentarios):

 public class Test { final static int CALLS = 710; public static void main(String[] args) { final Functor[] functors = new Functor[CALLS]; for (int i = 0; i < CALLS; i++) { final int finalInt = i; functors[i] = new Functor() { @Override public void fun() { System.out.print(finalInt + " "); if (finalInt != CALLS - 1) { functors[finalInt + 1].fun(); } } }; } // Let's get ready to ruuuuuuumble! functors[0].fun(); // Sorry, couldn't resist to not comment in such moment. } interface Functor { void fun(); } } 

Comstack con javac Test.java estándar y ejecuta con java -Xss104k Test 2> out . Después de eso, more out te dirá:

 Exception in thread "main" java.lang.StackOverflowError 

Segundo bash.

Ahora la idea es aún más simple. Los primitivos en Java se pueden almacenar en la stack. Entonces, declaremos muchos dobles, como double a1,a2,a3... Este script puede escribir, comstackr y ejecutar el código para nosotros:

 #!/bin/sh VARIABLES=4000 NAME=Test FILE=$NAME.java SOURCE="public class $NAME{public static void main(String[] args){double " for i in $(seq 1 $VARIABLES); do SOURCE=$SOURCE"a$i," done SOURCE=$SOURCE"b=0;System.out.println(b);}}" echo $SOURCE > $FILE javac $FILE java -Xss104k $NAME 

Y ... tengo algo inesperado:

 # # A fatal error has been detected by the Java Runtime Environment: # # SIGSEGV (0xb) at pc=0x00007f4822f9d501, pid=4988, tid=139947823249152 # # JRE version: 6.0_27-b27 # Java VM: OpenJDK 64-Bit Server VM (20.0-b12 mixed mode linux-amd64 compressed oops) # Derivative: IcedTea6 1.12.6 # Distribution: Ubuntu 10.04.1 LTS, package 6b27-1.12.6-1ubuntu0.10.04.2 # Problematic frame: # V [libjvm.so+0x4ce501] JavaThread::last_frame()+0xa1 # # An error report file with more information is saved as: # /home/adam/Desktop/test/hs_err_pid4988.log # # If you would like to submit a bug report, please include # instructions how to reproduce the bug and visit: # https://bugs.launchpad.net/ubuntu/+source/openjdk-6/ # Aborted 

Es 100% repetitivo. Esto está relacionado con tu segunda pregunta:

¿El StackOverflowError ocurre antes de que la JVM desborde realmente la stack o después?

Entonces, en el caso de OpenJDK 20.0-b12, podemos ver que JVM explotó primero. Pero parece un error, tal vez alguien puede confirmarlo en los comentarios, por favor, porque no estoy seguro. ¿Debo informar esto? Tal vez ya esté arreglado en alguna versión más nueva ... Según el enlace de especificación JVM (dado por JB Nizet en un comentario) JVM debería arrojar un StackOverflowError , no morir:

Si el cálculo en un subproceso requiere una stack Java Virtual Machine mayor de la permitida, la Máquina Virtual Java arroja un StackOverflowError.


Tercer bash

 public class Test { Test test = new Test(); public static void main(String[] args) { new Test(); } } 

Queremos crear un nuevo objeto de Test . Por lo tanto, se llamará a su constructor (implícito). Pero, justo antes de eso, todos los miembros de Test se inicializaron. Entonces, Test test = new Test() se ejecuta primero ...

Queremos crear un nuevo objeto de Test ...

Actualización: Mala suerte, esto es recursividad, hice una pregunta sobre eso aquí .

La causa más común de StackOverFlowError es una recurrencia excesivamente profunda o infinita.

Por ejemplo:

 public int yourMethod(){ yourMethod();//infinite recursion } 

En Java:

Hay two áreas en la memoria, el montón y la stack. La stack memory se usa para almacenar variables locales y llamada de función, mientras que la heap memory se usa para almacenar objetos en Java.

Si no queda memoria en la stack para almacenar llamadas de funciones o variables locales, JVM lanzará java.lang.StackOverFlowError

mientras que si no hay más espacio en el montón para crear objetos, JVM lanzará java.lang.OutOfMemoryError

No hay “StackOverFlowException”. Lo que quieres decir es “StackOverFlowError”.

Sí, puedes seguir trabajando si lo atrapas porque la stack se borra cuando lo haces, pero esa sería una opción mala y fea.

¿Cuándo exactamente se arroja el error? – Cuando llama a un método y la JVM verifica si hay suficiente memoria para hacerlo. Por supuesto, el error se produce si no es posible.

  • No, esa es la única forma en que puede obtener ese error: obtener su stack completa. Pero no solo a través de la recursión, también llamando a métodos que llaman infinitamente a otros métodos. Es un error muy específico, así que no.
  • Se lanza antes de que la stack esté llena, exactamente cuando la verificas. ¿Dónde colocarías los datos si no hay espacio disponible? ¿Anulando a los demás? Naah.

Hay dos lugares principales donde las cosas se pueden almacenar en Java. El primero es el Heap, que se usa para objetos dinámicamente asignados. new

Además, cada subproceso en ejecución obtiene su propia stack y obtiene una cantidad de memoria asignada a esa stack.

Cuando llama a un método, los datos se envían a la stack para registrar la llamada al método, los parámetros que se pasan y las variables locales que se asignan. Un método con cinco variables locales y tres parámetros usará más espacio de stack que un método void doStuff() sin variables locales.

Las principales ventajas de la stack son que no hay fragmentación de memoria, todo para una llamada de método se asigna en la parte superior de la stack, y que volver de los métodos es fácil. Para regresar de un método, simplemente vuelve a desenrollar la stack al método anterior, establece el valor necesario para el valor de retorno y listo.

Debido a que la stack tiene un tamaño fijo por hilo, (tenga en cuenta que Java Spec no requiere un tamaño fijo, pero la mayoría de las implementaciones de JVM al momento de escribir usan un tamaño fijo) y porque se necesita espacio en la stack cada vez que crea un método llame con suerte ahora debería estar claro por qué puede agotarse y qué puede hacer que se agote. No hay un número fijo de llamadas a métodos, no hay nada específico acerca de la recursión, se obtiene la excepción que intenta llamar a un método y no hay suficiente memoria.

Por supuesto, el tamaño de las stacks está configurado lo suficientemente alto como para que sea muy poco probable que suceda en el código regular. Sin embargo, en el código recursivo puede ser bastante fácil recurrir a grandes profundidades, y en ese punto empiezas a correr este error.

StackOverflowError produce debido a una aplicación recursas demasiado profundo (Esta no es una respuesta que está esperando).

Ahora otras cosas le pueden pasar a StackOverflowError es seguir llamando a los métodos hasta obtener StackOverflowError , pero nadie puede progtwigr para obtener StackOverflowError e incluso si esos progtwigdores lo están haciendo, entonces no están siguiendo los estándares de encoding para la compliancia ciclomática que todo progtwigdor debe entender mientras progtwigción. Tal razón para ‘StackOverflowError’ requerirá mucho tiempo para rectificarla.

Pero, sin saberlo, la encoding de una línea o dos líneas que causa StackOverflowError es comprensible y JVM lanza eso y podemos rectificarlo instantáneamente. Aquí está mi respuesta con la imagen para alguna otra pregunta.

Un StackOverflow ocurre cuando se realiza una llamada de función y la stack está llena.

Como una ArrayOutOfBoundException. No puede corromper nada, de hecho es muy posible atraparlo y recuperarse de él.

Suele ocurrir como resultado de una recursión descontrolada, pero también puede ser causada por simplemente tener una stack muy profunda de llamadas de funciones.

En c # puede lograr el desbordamiento de la stack de una manera diferente, al definir erróneamente las propiedades del objeto. Por ejemplo :

 private double hours; public double Hours { get { return Hours; } set { Hours = value; } } 

Como puede ver, esto siempre continuará volviendo Horas con una H mayúscula, que en sí misma devolverá Horas y así sucesivamente.

También se producirá un desbordamiento de stack debido a que se está quedando sin memoria o al uso de lenguajes administrados porque su administrador de idioma (CLR, JRE) detectará que su código se atascó en un bucle infinito.

Pero esto plantea dos preguntas:

  1. ¿No hay otras formas de que ocurra un desbordamiento de stack, no solo a través de la recursión?
  2. ¿El StackOverflowError ocurre antes de que la JVM desborde realmente la stack o después?
  1. También puede ocurrir cuando estamos asignando un tamaño mayor que el límite de la stack (por ejemplo, int x[10000000]; ).

  2. La respuesta a la segunda es

Cada hilo tiene su propia stack que contiene un marco para cada método que se ejecuta en ese hilo. Entonces, el método que se está ejecutando actualmente está en la parte superior de la stack. Se crea un nuevo marco y se agrega (empuja) a la parte superior de la stack para cada invocación de método. El marco se elimina (aparece) cuando el método retorna normalmente o si se lanza una excepción no detectada durante la invocación del método. La stack no se manipula directamente, excepto para empujar y abrir objetos de marco y, por lo tanto, los objetos de marco pueden asignarse en el Heap y la memoria no necesita estar contigua.

Entonces, al considerar la stack en un hilo, podemos concluir.

Una stack puede ser un tamaño dynamic o fijo. Si un hilo requiere una stack mayor de la permitida, se lanza StackOverflowError . Si un hilo requiere un nuevo marco y no hay suficiente memoria para asignarlo, se OutOfMemoryError un OutOfMemoryError .

puedes obtener una descripción de JVM aquí