Pregunta

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

¿Fue útil?

Solución

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 generalmente 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 se encuentra en el final final 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 & Quot; colisione & Quot; con la pila (¡un poco como las placas tectónicas!).

La causa común de un desbordamiento de pila es una llamada recursiva incorrecta . 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 de la GUI, es posible generar 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, pero el OS / VM lo ha hecho por ti.

Para tratar 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 finalizació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).

Otros consejos

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

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

Si miraba la imagen, debería 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, la máquina virtual Java (JVM) arroja el StackOverflowError.

El caso más común que posiblemente puede agotar una aplicación Java & # 8217; s stack es la recursión. En la recursividad, un método se invoca durante su ejecución. La recursión se considera como una poderosa técnica de programación de propósito general, pero debe usarse con precaución para evitar recursivePrint.

A continuación se muestra un ejemplo arrojando un 0:

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 -Xss1M que 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 -Xss 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, utilizando el indicador -Xss<size>[g|G|m|M|k|K] que especifica el tamaño de la pila de subprocesos 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 JVM & # 8217, los resultados pueden diferir, pero eventualmente se arrojará el <=>. 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 números de línea. Estos números de línea indica el código que se llama recursivamente. Una vez que detecte estos líneas, debe inspeccionar cuidadosamente su código y comprender por qué La recursión nunca termina.

  2. Si ha verificado que la recursividad     está implementado correctamente, puede aumentar el tamaño de la pila & # 8217; s, en     para permitir un mayor número de invocaciones. Dependiendo de Java     Máquina virtual (JVM) instalada, el tamaño predeterminado de la pila de subprocesos puede     igual a 512 KB o 1 MB . Puedes aumentar la pila de hilos     tamaño usando la bandera <=>. Esta bandera se puede especificar a través de     configuración del proyecto & # 8217; o a través de la línea de comando. El formato de la     <=> argumento es:     <=>

Si tiene una función como:

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

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

Desbordamiento de pila significa exactamente eso: se desborda una pila. 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 asigna demasiado en 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 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 aumentar el tamaño de tu stack si puedes. 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 que falla.

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 usar el montón sería más apropiado.

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

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

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

StackOverflowError está en la pila como OutOfMemoryError está en el montón.

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

El siguiente ejemplo produce <=>:

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

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

<=> 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).

Aquí hay un ejemplo de un algoritmo recursivo para invertir una lista vinculada individualmente. En una computadora portátil con las siguientes especificaciones (memoria 4G, CPU Intel Core i5 2.3GHz, Windows 7 de 64 bits), esta función se encontrará con un error StackOverflow para una lista vinculada de tamaño cercano 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. (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);
}

A StackOverflowError es un error de tiempo de ejecución en java.

Se genera cuando se excede la cantidad de memoria de pila de llamadas asignada por JVM.

Un caso común de un <=> lanzamiento, 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.

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 es básicamente cuando intentas hacer algo, lo más probable es que se llame 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.

Este es un caso típico de java.lang.StackOverflowError ... El método se llama recursivamente a sí mismo 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.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í es el código fuente de StackOverflowError en OpenJDK 7

El término " stack overrun (overflow) " a menudo se usa pero es un nombre inapropiado; los ataques no desbordan la pila sino que amortiguan la pila.

- de las diapositivas de Prof. Dr. Dieter Gollmann

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top