¿Qué es un StackOverflowError?

439

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

Ziggy
fuente
El tamaño de la pila en Java es pequeño. Y algunas veces, como muchas llamadas recursivas, enfrenta este problema. Puede rediseñar su código por bucle. Puede encontrar un patrón de diseño general para hacerlo en esta url: jndanial.com/73
JNDanial
Una forma no obvia de obtenerlo: agregue la línea new Object() {{getClass().newInstance();}};a algún contexto estático (por ejemplo, mainmétodo). No funciona desde el contexto de la instancia (solo tira InstantiationException).
John McClane

Respuestas:

408

Los parámetros y las variables locales se asignan en la pila (con los tipos de referencia, el objeto vive en el montón y una variable en la pila hace referencia a ese objeto en el montón). La pila normalmente vive en el extremo superior de su espacio de direcciones y, a medida que se agota, 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 se puede ver, hay un potencial para el montón de "chocan" con la pila (un poco como placas tectónicas !!!).

La causa común de un desbordamiento de pila es una mala llamada recursiva . Por lo general, esto se produce cuando sus funciones recursivas no tienen la condición de terminación correcta, por lo que termina llamándose para siempre. O cuando la condición de terminación es buena, puede ser causada por requerir demasiadas llamadas recursivas antes de cumplirla.

Sin embargo, con la programación GUI, es posible generar una recursión indirecta . Por ejemplo, su aplicación puede estar manejando 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 el OS / VM lo ha hecho por ti.

Para lidiar con ellos, deberá examinar su código. Si tiene funciones que se llaman a sí mismas, compruebe que tiene una condición de terminación. Si es así, verifique que al llamar a la función haya modificado al menos 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. También tenga en cuenta que su espacio de pila puede quedarse sin memoria antes de alcanzar una condición de terminación válida, por lo tanto, asegúrese de que su método pueda manejar los valores de entrada que requieren más llamadas recursivas.

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 el caso implícito anterior).

Sean
fuente
1
Cartel original: oye, esto es genial. Entonces, ¿la recursión siempre es responsable de los desbordamientos de la pila? ¿O pueden otras cosas ser responsables de ellos también? Lamentablemente estoy usando una biblioteca ... pero no una que entiendo.
Ziggy
44
Ja ja ja, así que aquí está: while (puntos <100) {addMouseListeners (); moveball (); checkforcollision (); pausa (velocidad);} Wow me siento cojo por no darme cuenta de que terminaría con un montón de oyentes de mouse ... ¡Gracias chicos!
Ziggy
44
No, los desbordamientos de la pila también pueden provenir de variables que son demasiado grandes para asignar en la pila si consulta el artículo de Wikipedia en es.wikipedia.org/wiki/Stack_overflow .
JB King
8
Cabe señalar que es casi imposible "manejar" un error de desbordamiento de pila. En la mayoría de los entornos, para manejar el error, se necesita ejecutar código en la pila, lo cual es difícil si no hay más espacio en la pila.
Hot Licks el
3
@JB King: Realmente no se aplica a Java, donde solo los tipos primitivos y las referencias se mantienen en la pila. Todas las cosas grandes (matrices y objetos) están en el montón.
jcsahnwaldt dice GoFundMonica el
107

Para describir esto, primero comprendamos cómo se almacenan las variables y los objetos locales .

Las variables locales se almacenan en la pila : ingrese la descripción de la imagen aquí

Si mirabas 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 la 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 indica 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, StackOverflowErrorJava Virtual Machine (JVM) lo lanza.

El caso más común que posiblemente puede agotar la pila de una aplicación Java es la recursividad. En la recursividad, un método se invoca durante su ejecución. La recursión se considera una técnica de programación poderosa de uso general, pero debe usarse con precaución para evitarla StackOverflowError.

A continuación se muestra un ejemplo de tirar un StackOverflowError:

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 recursivePrintque imprime un número entero y luego se llama a sí mismo, con el siguiente número entero sucesivo como argumento. La recursión termina hasta que pasamos 0como 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, utilizando el -Xss1Mindicador que especifica el tamaño de la pila 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 StackOverflowErrorse arrojarán. 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 StackOverflowError

  1. La solución más simple es inspeccionar cuidadosamente el seguimiento de la pila y detectar el patrón repetitivo 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.

  2. 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 predeterminado de la pila de subprocesos puede ser igual a 512 KB o 1 MB . Puede aumentar el tamaño de la pila de subprocesos utilizando la -Xssbandera. Este indicador se puede especificar mediante la configuración del proyecto o mediante la línea de comando. El formato del -Xssargumento es: -Xss<size>[g|G|m|M|k|K]

Varun
fuente
Parece que hay un error en algunas versiones de Java cuando se usa Windows donde el argumento -Xss solo tiene efecto en los nuevos hilos
goerlibe
65

Si tiene una función como:

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

Luego, 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 se encuentra, se obtiene el error de desbordamiento de pila.

Khoth
fuente
12
Incorrecto. Su función es recursiva de cola. La mayoría de los lenguajes compilados tienen optimizaciones de recursión de cola. Esto significa que la recursión se reduce a un bucle simple y nunca alcanzará el desbordamiento de pila con este código en algunos sistemas.
Alegre
Alegre, ¿qué lenguajes no funcionales admiten la recursividad de cola?
horseyguy
@banister y algunas implementaciones de javascript
Pacerier
@horseyguy Scala tiene soporte para la recursividad de cola.
Ajit K'sagar
Esto captura la esencia de lo que puede crear un desbordamiento de pila. Agradable.
Pixel
24

El desbordamiento de pila significa exactamente eso: una pila se desborda. Por lo general, hay una pila en el programa que contiene variables de ámbito local y direcciones donde 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á limitado cuánto puede contener valores.

Si la pila está vacía, no puede aparecer, 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 la pila aparece donde se asigna demasiado a la pila. Por ejemplo, en la recursión mencionada.

Algunas implementaciones optimizan algunas formas de recursiones. Recurrencia de la cola en particular. Las rutinas recursivas de cola son formas de rutinas donde la llamada recursiva aparece como una cosa final de lo que hace la rutina. Tal llamada de rutina simplemente se reduce a un salto.

Algunas implementaciones llegan a implementar sus propias pilas para la recursividad, 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ía intentar sería aumentar el tamaño de su pila si puede. Sin embargo, si no puede hacer eso, la segunda mejor opción sería mirar si hay algo que claramente causa el desbordamiento de la pila. Pruébelo imprimiendo algo antes y después de la llamada en la rutina. Esto le ayuda a descubrir la rutina fallida.

Alegre
fuente
44
¿Existe tal cosa como un desbordamiento de pila ?
Pacerier
55
Es posible un desbordamiento de pila en el ensamblaje (apareciendo más de lo que presionó), aunque en lenguajes compilados sería casi imposible. No estoy seguro, es posible que pueda encontrar una implementación de alloca () de C que "admita" tamaños negativos.
Score_Under
2
El desbordamiento de pila significa exactamente eso: un desbordamiento de pila. Por lo general, hay una pila en el programa que contiene variables de ámbito local -> No, cada subproceso tiene su propia pila que contiene marcos de pila para cada invocación de método que contiene variables locales ..
Koray Tugay
9

Un desbordamiento de la pila generalmente se llama mediante llamadas 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 al asignar una gran cantidad de memoria en la pila donde sería más apropiado usar el montón.

Greg
fuente
1
Vaya, no vi la etiqueta de Java
Greg
Además, desde el póster original aquí: ¿el anidamiento funciona demasiado profundamente en qué? ¿Otras funciones? Y: ¿cómo se asigna memoria a la pila o al montón? (Ya que, ya sabes, claramente he hecho una de estas cosas sin saberlo).
Ziggy
@Ziggy: Sí, si una función llama a otra función, que llama a otra función, y así sucesivamente, después de muchos niveles, su programa tendrá un desbordamiento de pila. [continúa]
Chris Jester-Young
[... continúa] En Java, no puede asignar directamente memoria de la pila (mientras que en C sí puede, y esto sería algo a tener en cuenta), por lo que es poco probable que sea la causa. En Java, todas las asignaciones directas provienen del montón, mediante el uso de "nuevo".
Chris Jester-Young
@ ChrisJester-Young ¿No es cierto que si tengo 100 variables locales en un método, todo va a la pila sin excepciones?
Pacerier
7

Como usted dice, necesita mostrar un código. :-)

Un error de desbordamiento de pila generalmente ocurre cuando las llamadas a funciones anidan demasiado. Vea el hilo de Stack Overflow Code Golf para ver algunos ejemplos de cómo sucede esto (aunque en el caso de esa pregunta, las respuestas intencionalmente causan un desbordamiento de la pila).

Chris Jester-Young
fuente
1
Me gustaría totalmente agregar código, pero como no sé qué causa los desbordamientos de la pila, no estoy seguro de qué código agregar. agregar todo el código sería cojo, ¿no?
Ziggy
¿Su proyecto es de código abierto? Si es así, solo crea una cuenta de Sourceforge o github y sube todo tu código allí. :-)
Chris Jester-Young
Esto suena como una gran idea, pero soy tan novato que ni siquiera sé lo que tendría que cargar. Al igual, la biblioteca que estoy importando clases que estoy extendiendo, etc ... son todas desconocidas para mí. Oh hombre: malos tiempos.
Ziggy
5

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 ayudarlo a comprender el problema.

splattne
fuente
5

StackOverflowError es a la pila como OutOfMemoryError para el montón.

Las llamadas recursivas ilimitadas dan como resultado que se agote el espacio de la pila.

El siguiente ejemplo produce StackOverflowError:

class  StackOverflowDemo
{
    public static void unboundedRecursiveCall() {
     unboundedRecursiveCall();
    }

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

StackOverflowError es evitable 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 pila (en bytes).

Vikram
fuente
3

Aquí hay un ejemplo de un algoritmo recursivo para invertir 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 encontrará con el error StackOverflow para una lista vinculada de tamaño cercano a 10,000.

Mi punto es que deberíamos usar la recursividad 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. (Una versión iterativa del mismo algoritmo se da en la parte inferior de la página, invierte una lista individualmente vinculada 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);
}
Yiling
fuente
Creo que con JVM, en realidad no importa cuál sea la especificación de su computadora portátil.
Kevin
3

A StackOverflowErrores 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 StackOverflowErrorser arrojado es cuando la pila de llamadas 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");
    }
}

Seguimiento de pila:

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, se puede evitar haciendo cambios programáticos. Pero si la lógica del programa es correcta y aún se produce, entonces debe aumentar el tamaño de la pila.

Rahul Sah
fuente
0

Este es un caso típico de java.lang.StackOverflowError... El método se llama recursivamente a sí mismo sin salir doubleValue(),floatValue() , etc.

Racional.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.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 StackOverflowErrorOpenJDK 7


fuente
0

En una crisis, la situación a continuación traerá un error de desbordamiento de pila.

public class Example3 {

public static void main(String[] args) {

    main(new String[1]);

}

}

Kaliappan
fuente
-1

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;
}

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

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

John S.
fuente
-1

El término "desbordamiento de pila (desbordamiento)" se usa a menudo pero es un nombre inapropiado; Los ataques no desbordan la pila, sino que los amortigua en la pila.

- de las diapositivas del profesor Dr. Dieter Gollmann

Sergiu
fuente