java - ¿Qué es un StackOverflowError?




exception-handling stack-overflow (12)

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

¿Qué es un Error , 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).


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

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


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


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

  1. 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.

  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 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]


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


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.


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


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.


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.


Qué tal si:

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.IOException;    

public static String readInputStreamAsString(InputStream in) 
    throws IOException {

    BufferedInputStream bis = new BufferedInputStream(in);
    ByteArrayOutputStream buf = new ByteArrayOutputStream();
    int result = bis.read();
    while(result != -1) {
      byte b = (byte)result;
      buf.write(b);
      result = bis.read();
    }        
    return buf.toString();
}




java exception-handling stack-overflow