¿Qué es un StackOverflowError?

¿Qué es un StackOverflowError , qué lo causa y cómo debo manejarlo?

Los parámetros y las variables locales se asignan en la stack (con los tipos de referencia, el objeto vive en el montón y una variable hace referencia a ese objeto). La stack generalmente vive en el extremo superior de su espacio de direcciones y, a medida que se utiliza, se dirige hacia la parte inferior del espacio de direcciones (es decir, hacia cero).

Su proceso también tiene un montón , que vive en el extremo inferior de su proceso. A medida que asigna memoria, este montón puede crecer hacia el extremo superior de su espacio de direcciones. Como puede ver, existe la posibilidad de que el montón “colisione” con la stack (¡un poco como las placas tectónicas!).

La causa común para un desbordamiento de stack es una mala llamada recursiva . Normalmente, esto se produce cuando sus funciones recursivas no tienen la condición de terminación correcta, por lo que termina llamándose para siempre.

Sin embargo, con la progtwigción de GUI, es posible generar recursividad indirecta . Por ejemplo, su aplicación puede estar manipulando mensajes de pintura y, mientras los procesa, puede llamar a una función que hace que el sistema envíe otro mensaje de pintura. Aquí no se ha llamado explícitamente a sí mismo, pero OS / VM lo ha hecho por usted.

Para lidiar con ellos, tendrá que examinar su código. Si tiene funciones que se llaman a sí mismas, compruebe que tiene una condición de terminación. Si tiene, entonces verifique que al llamar a la función al menos haya modificado uno de los argumentos, de lo contrario no habrá cambios visibles para la función llamada recursivamente y la condición de terminación es inútil.

Si no tiene funciones recursivas obvias, compruebe si está llamando a cualquier función de biblioteca que indirectamente haga que se llame a su función (como en el caso implícito anterior).

Para describir esto, primero déjenos entender cómo se almacenan las variables y los objetos locales .

La variable local se almacena en la stack : enter image description here

Si miras la imagen, deberías poder entender cómo funcionan las cosas.

Cuando una invocación de función es invocada por una aplicación Java, se asigna un marco de stack en la stack de llamadas. El marco de stack contiene los parámetros del método invocado, sus parámetros locales y la dirección de retorno del método. La dirección de retorno indica el punto de ejecución desde el cual, la ejecución del progtwig continuará después de que el método invocado regrese. Si no hay espacio para un nuevo marco de stack, la máquina virtual de Java (JVM) lanza el StackOverflowError .

El caso más común que puede agotar la stack de una aplicación Java es la recursión. En recursión, un método se invoca a sí mismo durante su ejecución. La recursividad se considera una poderosa técnica de progtwigción de propósito general, pero debe usarse con precaución para evitar StackOverflowError .

Un ejemplo que arroja un StackOverflowError se muestra a continuación:

StackOverflowErrorExample.java:

 public class StackOverflowErrorExample { public static void recursivePrint(int num) { System.out.println("Number: " + num); if(num == 0) return; else recursivePrint(++num); } public static void main(String[] args) { StackOverflowErrorExample.recursivePrint(1); } } 

En este ejemplo, definimos un método recursivo, llamado recursivePrint que imprime un entero y luego, se llama a sí mismo, con el siguiente número entero sucesivo como argumento. La recursión termina hasta que pasemos en 0 como parámetro. Sin embargo, en nuestro ejemplo, pasamos el parámetro de 1 y sus seguidores crecientes, por lo tanto, la recursión nunca terminará.

A continuación, se muestra una ejecución de muestra, que utiliza el -Xss1M que especifica el tamaño de la stack de subprocesos para que sea igual a 1 MB.

 Number: 1 Number: 2 Number: 3 ... Number: 6262 Number: 6263 Number: 6264 Number: 6265 Number: 6266 Exception in thread "main" java.lang.StackOverflowError at java.io.PrintStream.write(PrintStream.java:480) at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221) at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291) at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104) at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185) at java.io.PrintStream.write(PrintStream.java:527) at java.io.PrintStream.print(PrintStream.java:669) at java.io.PrintStream.println(PrintStream.java:806) at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:4) at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:9) at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:9) at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:9) ... 

Dependiendo de la configuración inicial de la JVM, los resultados pueden diferir, pero eventualmente se StackOverflowError el StackOverflowError . Este ejemplo es un muy buen ejemplo de cómo la recursión puede causar problemas, si no se implementa con precaución.

Cómo lidiar con StackOverflowError

  1. La solución más simple es inspeccionar cuidadosamente el trazado de la stack y detectar el patrón repetitivo de los números de línea. Estos números de línea indican que el código se llama recursivamente. Una vez que detecta estas líneas, debe inspeccionar cuidadosamente su código y comprender por qué la recursión nunca termina.

  2. Si ha verificado que la recursión se implementa correctamente, puede boost el tamaño de la stack para permitir un mayor número de invocaciones. Dependiendo de la máquina virtual Java (JVM) instalada, el tamaño predeterminado de la stack de subprocesos puede ser de 512 KB o 1 MB . Puede boost el tamaño de la stack de hilos usando el indicador -Xss . Este indicador se puede especificar a través de la configuración del proyecto o a través de la línea de comando. El formato del argumento -Xss es: -Xss[g|G|m|M|k|K]

Si tiene una función como:

 int foo() { // more stuff foo(); } 

Entonces foo () seguirá llamándose a sí mismo, haciéndose cada vez más profundo, y cuando el espacio utilizado para realizar un seguimiento de las funciones en las que se encuentra se completa, se obtiene el error de desbordamiento de la stack.

El desbordamiento de stack significa exactamente eso: una stack se desborda. Por lo general, hay una stack en el progtwig que contiene variables de ámbito local y direcciones a las que regresar cuando finaliza la ejecución de una rutina. Esa stack tiende a ser un rango de memoria fijo en algún lugar de la memoria, por lo tanto, está limitada en la cantidad que puede contener valores.

Si la stack está vacía, no se puede abrir, si lo haces obtendrás un error de subdesbordamiento de la stack.

Si la stack está llena no puedes empujar, si lo haces obtendrás un error de desbordamiento de stack.

Así que el desbordamiento de la stack aparece donde se asigna demasiado a la stack. Por ejemplo, en la recursión mencionada.

Algunas implementaciones optimizan algunas formas de recursiones. Recursividad de cola en particular. Las rutinas recursivas de cola son formas de rutinas en las que la llamada recursiva aparece como lo último que hace la rutina. Tal llamada de rutina simplemente se reduce a un salto.

Algunas implementaciones llegan incluso a implementar sus propios stacks para la recursión, por lo tanto, permiten que la recursión continúe hasta que el sistema se quede sin memoria.

Lo más fácil que podrías intentar sería boost el tamaño de tu stack si puedes. Sin embargo, si no puedes hacer eso, la segunda mejor opción sería ver si hay algo que claramente causa el desbordamiento de la stack. Pruébelo imprimiendo algo antes y después de la llamada a la rutina. Esto te ayuda a descubrir la rutina fallida.

Un desbordamiento de stack generalmente se llama por llamadas de función de anidamiento demasiado profundo (especialmente fácil cuando se usa recursividad, es decir, una función que se llama a sí misma) o asignando una gran cantidad de memoria en la stack donde sería más apropiado usar el montón.

Como dices, necesitas mostrar un código. 🙂

Un error de desbordamiento de la stack generalmente ocurre cuando las llamadas de su función anidan demasiado profundamente. Consulte el hilo del Golf de código de desbordamiento de stack para ver ejemplos de cómo sucede esto (aunque en el caso de esa pregunta, las respuestas causan desbordamiento de stack intencionalmente).

StackOverflowError está en la stack ya que OutOfMemoryError está en el montón.

Las llamadas recursivas ilimitadas provocan que se agote el espacio de la stack.

El siguiente ejemplo produce StackOverflowError :

 class StackOverflowDemo { public static void unboundedRecursiveCall() { unboundedRecursiveCall(); } public static void main(String[] args) { unboundedRecursiveCall(); } } 

StackOverflowError se puede evitar si las llamadas recursivas están limitadas para evitar que el total agregado de llamadas incompletas en memoria (en bytes) exceda el tamaño de la stack (en bytes).

La causa más común de desbordamientos de stack es una recursión excesivamente profunda o infinita . Si este es su problema, este tutorial sobre Java Recursion podría ayudar a comprender el problema.

Aquí hay un ejemplo de un algoritmo recursivo para revertir una lista vinculada individualmente. En una computadora portátil con la siguiente especificación (memoria 4G, CPU Intel Core i5 2.3GHz, Windows 7 de 64 bits), esta función se ejecutará en el error de StackOverflow para una lista vinculada de tamaño cercano a 10.000.

Mi punto es que debemos usar la recursión juiciosamente, siempre teniendo en cuenta la escala del sistema. A menudo, la recursividad se puede convertir a un progtwig iterativo, que se escala mejor. (Se proporciona una versión iterativa del mismo algoritmo en la parte inferior de la página, invierte una lista enlazada individualmente de tamaño 1 millón en 9 milisegundos).

  private static LinkedListNode doReverseRecursively(LinkedListNode x, LinkedListNode first){ LinkedListNode second = first.next; first.next = x; if(second != null){ return doReverseRecursively(first, second); }else{ return first; } } public static LinkedListNode reverseRecursively(LinkedListNode head){ return doReverseRecursively(null, head); } 

Versión iterativa del mismo algoritmo:

  public static LinkedListNode reverseIteratively(LinkedListNode head){ return doReverseIteratively(null, head); } private static LinkedListNode doReverseIteratively(LinkedListNode x, LinkedListNode first) { while (first != null) { LinkedListNode second = first.next; first.next = x; x = first; if (second == null) { break; } else { first = second; } } return first; } public static LinkedListNode reverseIteratively(LinkedListNode head){ return doReverseIteratively(null, head); } 

Un StackOverflowError es un error de tiempo de ejecución en Java.

Se lanza cuando se excede la cantidad de memoria de la stack de llamadas asignada por JVM.

Un caso común de un StackOverflowError se lanza cuando la stack de llamadas se excede debido a una recursión excesiva o infinita excesiva.

Ejemplo:

 public class Factorial { public static int factorial(int n){ if(n == 1){ return 1; } else{ return n * factorial(n-1); } } public static void main(String[] args){ System.out.println("Main method started"); int result = Factorial.factorial(-1); System.out.println("Factorial ==>"+result); System.out.println("Main method ended"); } } 

Stack trace:

 Main method started Exception in thread "main" java.lang.StackOverflowError at com.program.stackoverflow.Factorial.factorial(Factorial.java:9) at com.program.stackoverflow.Factorial.factorial(Factorial.java:9) at com.program.stackoverflow.Factorial.factorial(Factorial.java:9) 

En el caso anterior, puede evitarse haciendo cambios programáticos. Pero si la lógica del progtwig es correcta y aún ocurre, entonces el tamaño de la stack debe boostse.

Aquí hay un ejemplo

 public static void main(String[] args) { System.out.println(add5(1)); } public static int add5(int a) { return add5(a) + 5; } 

Un StackOverflowError básicamente es cuando intenta hacer algo, que muy probablemente se llama a sí mismo, y continúa hasta el infinito (o hasta que da un StackOverflowError).

add5(a) se llamará a sí mismo, y luego se volverá a llamar a sí mismo, y así sucesivamente.

El término “desbordamiento de stack (desbordamiento)” se usa a menudo pero es un nombre inapropiado; los ataques no se desbordan en la stack, sino que se almacenan en la stack.

– de diapositivas de la conferencia del Prof. Dr. Dieter Gollmann

Este es un caso típico de java.lang.StackOverflowError … El método se llama de forma recursiva sin salir en doubleValue() , floatValue() , etc.

Rational.java

  public class Rational extends Number implements Comparable { private int num; private int denom; public Rational(int num, int denom) { this.num = num; this.denom = denom; } public int compareTo(Rational r) { if ((num / denom) - (r.num / r.denom) > 0) { return +1; } else if ((num / denom) - (r.num / r.denom) < 0) { return -1; } return 0; } public Rational add(Rational r) { return new Rational(num + r.num, denom + r.denom); } public Rational sub(Rational r) { return new Rational(num - r.num, denom - r.denom); } public Rational mul(Rational r) { return new Rational(num * r.num, denom * r.denom); } public Rational div(Rational r) { return new Rational(num * r.denom, denom * r.num); } public int gcd(Rational r) { int i = 1; while (i != 0) { i = denom % r.denom; denom = r.denom; r.denom = i; } return denom; } public String toString() { String a = num + "/" + denom; return a; } public double doubleValue() { return (double) doubleValue(); } public float floatValue() { return (float) floatValue(); } public int intValue() { return (int) intValue(); } public long longValue() { return (long) longValue(); } } 

Main.java

  public class Main { public static void main(String[] args) { Rational a = new Rational(2, 4); Rational b = new Rational(2, 6); System.out.println(a + " + " + b + " = " + a.add(b)); System.out.println(a + " - " + b + " = " + a.sub(b)); System.out.println(a + " * " + b + " = " + a.mul(b)); System.out.println(a + " / " + b + " = " + a.div(b)); Rational[] arr = {new Rational(7, 1), new Rational(6, 1), new Rational(5, 1), new Rational(4, 1), new Rational(3, 1), new Rational(2, 1), new Rational(1, 1), new Rational(1, 2), new Rational(1, 3), new Rational(1, 4), new Rational(1, 5), new Rational(1, 6), new Rational(1, 7), new Rational(1, 8), new Rational(1, 9), new Rational(0, 1)}; selectSort(arr); for (int i = 0; i < arr.length - 1; ++i) { if (arr[i].compareTo(arr[i + 1]) > 0) { System.exit(1); } } Number n = new Rational(3, 2); System.out.println(n.doubleValue()); System.out.println(n.floatValue()); System.out.println(n.intValue()); System.out.println(n.longValue()); } public static > void selectSort(T[] array) { T temp; int mini; for (int i = 0; i < array.length - 1; ++i) { mini = i; for (int j = i + 1; j < array.length; ++j) { if (array[j].compareTo(array[mini]) < 0) { mini = j; } } if (i != mini) { temp = array[i]; array[i] = array[mini]; array[mini] = temp; } } } } 

Resultado

  2/4 + 2/6 = 4/10 Exception in thread "main" java.lang.StackOverflowError 2/4 - 2/6 = 0/-2 at com.xetrasu.Rational.doubleValue(Rational.java:64) 2/4 * 2/6 = 4/24 at com.xetrasu.Rational.doubleValue(Rational.java:64) 2/4 / 2/6 = 12/8 at com.xetrasu.Rational.doubleValue(Rational.java:64) at com.xetrasu.Rational.doubleValue(Rational.java:64) at com.xetrasu.Rational.doubleValue(Rational.java:64) at com.xetrasu.Rational.doubleValue(Rational.java:64) at com.xetrasu.Rational.doubleValue(Rational.java:64) 

Aquí está el código fuente de StackOverflowError en OpenJDK 7