java - ¿Qué es un StackOverflowError?
exception-handling stack-overflow (13)
Aquí 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 intentas hacer algo, que 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 llamará a sí mismo de nuevo, y así sucesivamente.
¿Qué es un StackOverflowError
, qué lo causa y cómo debo tratar con ellos?
Como dices, necesitas mostrar algún código. :-)
Un error de desbordamiento de pila usualmente ocurre cuando su función llama al nido demasiado profundamente. Consulte el hilo del Código de desbordamiento de pila para ver algunos ejemplos de cómo sucede esto (aunque en el caso de esa pregunta, las respuestas provocan el desbordamiento de la pila).
Desbordamiento de pila significa exactamente eso: una pila se desborda. Por lo general, hay una pila en el programa que contiene variables de alcance local y direcciones a dónde regresar cuando finaliza la ejecución de una rutina. Esa pila tiende a ser un rango de memoria fijo en algún lugar de la memoria, por lo tanto, está limitada por la cantidad de valores que puede contener.
Si la pila está vacía, no puede saltar, si lo hace obtendrá un error de desbordamiento de pila.
Si la pila está llena, no puede empujar, si lo hace obtendrá un error de desbordamiento de pila.
Entonces, el desbordamiento de pila aparece cuando asignas demasiado en la pila. Por ejemplo, en la recursión mencionada.
Algunas implementaciones optimizan algunas formas de recursiones. Recursión de cola en particular. Las rutinas recursivas de cola son una forma de rutinas donde la llamada recursiva aparece como una cosa final de lo que hace la rutina. Tal llamada rutinaria se reduce simplemente en un salto.
Algunas implementaciones van tan lejos como para implementar sus propias pilas para la recursión, por lo que 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 aumentar el tamaño de tu pila si puedes. Sin embargo, si no puedes hacer eso, lo mejor sería mirar si hay algo que claramente provoca el desbordamiento de la pila. Pruébelo imprimiendo algo antes y después de la llamada en la rutina. Esto te ayuda a descubrir la rutina que falla.
El término "desbordamiento de pila (desbordamiento)" se usa a menudo, pero es un nombre inapropiado; los ataques no desbordan la pila sino que almacenan buffers en la pila.
- de las diapositivas del profesor Dr. Dieter Gollmann
Este es un caso típico de java.lang.Error
... El método se llama a sí mismo recursivamente sin salida en doubleValue()
, floatValue()
, etc.
Rational.java
public class Rational extends Number implements Comparable<Rational> {
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 <T extends Comparable<? super T>> 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.Error
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)
Este es un ejemplo de un algoritmo recursivo para revertir una lista enlazada 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 producirá un error de para una lista vinculada de tamaño cercana a 10,000.
Mi punto es que debemos usar la recursión con prudencia, siempre teniendo en cuenta la escala del sistema. A menudo, la recursión se puede convertir en un programa iterativo, que se escala mejor. (Se proporciona una versión iterativa del mismo algoritmo en la parte inferior de la página, e 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);
}
La causa más común de desbordamientos de pila es una recursión excesivamente profunda o infinita . Si este es su problema, este tutorial sobre Java Recursion podría ayudar a entender el problema.
Los parámetros y las variables locales se asignan en la pila (con tipos de referencia, el objeto vive en el montón y una variable hace referencia a ese objeto). La pila normalmente 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, hay un potencial para que el montón "choque" con la pila (¡¡un poco como las placas tectónicas !!!).
La causa común de un desbordamiento de pila es una llamada recursiva incorrecta . Normalmente, esto se produce cuando las funciones recursivas no tienen la condición de terminación correcta, por lo que termina llamándose a sí misma para siempre.
Sin embargo, con la programación GUI, es posible generar recursión indirecta . Por ejemplo, su aplicación puede manejar 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 te has llamado explícitamente a ti mismo, pero OS / VM lo ha hecho por ti.
Para lidiar con ellos, necesitarás examinar tu código. Si tiene funciones que se llaman a sí mismas, verifique que tenga una condición de terminación. Si lo ha hecho, entonces verifique que cuando llame a la función, al menos haya modificado uno de los argumentos, de lo contrario no habrá un cambio visible para la función llamada recursivamente y la condición de terminación es inútil.
Si no tiene funciones recursivas obvias, verifique si está llamando a alguna función de biblioteca que indirectamente hará que se llame a su función (como en el caso implícito anterior).
Para describir esto, primero entendamos cómo se almacenan las variables locales y los objetos.
Las variables locales se almacenan en la pila :
Si miras la imagen, deberías poder entender cómo funcionan las cosas.
Cuando una aplicación Java invoca una llamada de función, se asigna un marco de pila en la pila de llamadas. El marco de pila 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 denota el punto de ejecución desde el cual, la ejecución del programa continuará después de que regrese el método invocado. Si no hay espacio para un nuevo marco de pila, entonces el Error
es lanzado por la Máquina Virtual de Java (JVM).
El caso más común que puede agotar la pila de una aplicación Java es la recursión. En recursión, un método se invoca durante su ejecución. La recursión se considera una poderosa técnica de programación de propósito general, pero se debe usar con precaución para evitar Error
.
A continuación se muestra un ejemplo lanzando un Error
:
ErrorExample.java:
public class ErrorExample {
public static void recursivePrint(int num) {
System.out.println("Number: " + num);
if(num == 0)
return;
else
recursivePrint(++num);
}
public static void main(String[] args) {
ErrorExample.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 entero sucesivo como argumento. La recursión termina hasta que pasamos en 0
como parámetro. Sin embargo, en nuestro ejemplo, pasamos el parámetro de 1 y sus seguidores crecientes, por lo que la recursión nunca terminará.
A continuación se muestra una ejecución de muestra, que utiliza el indicador -Xss1M
que especifica el tamaño de la pila de hilos 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.Error
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 ErrorExample.recursivePrint(ErrorExample.java:4)
at ErrorExample.recursivePrint(ErrorExample.java:9)
at ErrorExample.recursivePrint(ErrorExample.java:9)
at ErrorExample.recursivePrint(ErrorExample.java:9)
...
Dependiendo de la configuración inicial de la JVM, los resultados pueden diferir, pero eventualmente se Error
el Error
. 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 el Error
La solución más simple es inspeccionar cuidadosamente la traza de la pila y detectar el patrón de repetición de los números de línea. Estos números de línea indican el código que se llama recursivamente. Una vez que detecte estas líneas, debe inspeccionar cuidadosamente su código y comprender por qué la recursión nunca termina.
Si ha verificado que la recursión se implementa correctamente, puede aumentar el tamaño de la pila para permitir un mayor número de invocaciones. Dependiendo de la máquina virtual Java (JVM) instalada, el tamaño de pila de subprocesos predeterminado puede ser igual a 512 KB o 1 MB . Puedes aumentar el tamaño de la pila de hilos usando la bandera
-Xss
. Este indicador se puede especificar a través de la configuración del proyecto o a través de la línea de comandos. El formato del argumento-Xss
es:-Xss<size>[g|G|m|M|k|K]
Si tienes una función como:
int foo()
{
// more stuff
foo();
}
Entonces foo () seguirá llamándose a sí mismo, cada vez más profundo, y cuando se llena el espacio utilizado para realizar un seguimiento de las funciones en las que está, se obtiene el error de desbordamiento de pila.
Un Error
es un error de tiempo de ejecución en java.
Se lanza cuando se excede la cantidad de memoria de pila de llamadas asignada por JVM.
Un caso común de aa Error
se lanza cuando la pila de llamadas excede debido a una excesiva recursión profunda o infinita.
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");
}
}
Rastreo de pila:
Main method started
Exception in thread "main" java.lang.Error
at com.program..Factorial.factorial(Factorial.java:9)
at com.program..Factorial.factorial(Factorial.java:9)
at com.program..Factorial.factorial(Factorial.java:9)
En el caso anterior se puede evitar hacer cambios programáticos. Pero si la lógica del programa es correcta y aún así ocurre, entonces se debe aumentar el tamaño de la pila.
Un desbordamiento de pila generalmente se llama a funciones de anidamiento demasiado profundas (especialmente fácil cuando se usa la recursión, es decir, una función que se llama a sí misma) o asignando una gran cantidad de memoria en la pila donde sería más apropiado usar el montón.
Error
es para la pila como OutOfMemoryError
es para el montón.
Las llamadas recursivas no unidas provocan que el espacio de pila se agote.
El siguiente ejemplo produce Error
:
class Demo
{
public static void unboundedRecursiveCall() {
unboundedRecursiveCall();
}
public static void main(String[] args)
{
unboundedRecursiveCall();
}
}
Error
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 pila (en bytes).