O que é um StackOverflowError?

O que é um, o que o causa, e como devo lidar com eles?

Author: Ziggy, 2008-10-18

13 answers

Os parâmetros e as variáveis locais são atribuídos na pilha (com tipos de referência o objecto vive no heap e uma variável de referências que o objecto). A pilha normalmente vive no final superior do seu espaço de endereços e, à medida que é usado para cima, dirige-se para o fundo do espaço de endereços (ou seja, para zero).

O teu processo também tem umheap , que vive nofinal do teu processo. Como você alocar memória, este heap pode crescer para a extremidade superior do seu espaço de endereçamento. Como você pode ver, há um potencial para o heap para "colidir" com a pilha (um pouco como placas tectônicas!!!).

A causa comum para um estouro de pilha é uma chamada recursiva {[[2]}má. Normalmente, isso é causado quando suas funções recursivas não têm a condição correta de terminação, então ele acaba se chamando para sempre.

No entanto, com a programação GUI, é possível gerar {[[2]}indiretos recursão. Por exemplo, o seu aplicativo pode estar lidando com mensagens de tinta, e, ao processá-las, pode chamar uma função que faz com que o sistema envie outra mensagem de tinta. Aqui você não se chamou explicitamente, mas o OS/VM fez isso por você. Para lidar com eles, terá de examinar o seu código. Se você tem funções que se chamam então verifique se você tem uma condição de terminação. Se você tem, em seguida, verifique que ao chamar a função você tem pelo menos modificado um dos argumentos, caso contrário não haverá mudança visível para a função recursivamente chamada e a condição de terminação é inútil.

Se não tem funções recursivas óbvias, então verifique se está a chamar alguma função de biblioteca que indirectamente fará com que a sua função seja chamada (como o caso implícito acima).

 336
Author: Sean, 2018-06-07 09:08:32

Para descrever isto, primeiro vamos entender como variáveis e objetos locais são armazenados.

As variáveis locais são armazenadas na pilha : enter image description here

Se você olhar para a imagem você deve ser capaz de entender como as coisas estão funcionando.

Quando uma chamada de função é invocada por uma aplicação Java, uma pilha de pilha é alocada na pilha de chamadas. A pilha contém os parâmetros do método invocado, seus parâmetros locais, e o endereço de retorno do método. O endereço de retorno denota o ponto de execução a partir do qual, a execução do programa deve continuar após o método invocado retorna. Se não houver espaço para uma nova estrutura de pilha então, o StackOverflowError é lançado pela máquina virtual Java (JVM).

O caso mais comum que pode possivelmente esgotar a pilha de uma aplicação Java é a recursão. Na recursão, um método invoca-se durante sua execução. Recursão é considerada como uma poderosa técnica de programação de propósito geral, mas deve ser utilizado com precaução, para evitar StackOverflowError.

Um exemplo de lançamento de um StackOverflowError é mostrado abaixo:

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

Neste exemplo, nós definimos um método recursivo, chamado recursivePrint que imprime um inteiro e, em seguida, se chama, com o próximo inteiro sucessivo como um argumento. A recursão termina até que passemos em 0 como um parâmetro. No entanto, em nosso exemplo, passamos no parâmetro de 1 e seus seguidores crescentes, consequentemente a recursão nunca terminará.

Uma execução de amostras, usando a opção -Xss1M que especifica o tamanho da pilha de linhas igual a 1MB, é mostrada abaixo:

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

Dependendo da configuração inicial da JVM, os resultados podem diferir, mas eventualmente o StackOverflowError será lançado. Este exemplo é um bom exemplo de como a recursão pode causar problemas, se não for implementada com cautela.

Como lidar com o StackOverflowError

  1. O a solução mais simples é inspecionar cuidadosamente o traço da pilha e detecte o padrão repetitivo de números de linha. Estes números de linha indique o código sendo chamado recursivamente. Uma vez que você detectar estes linhas, você deve inspecionar cuidadosamente o seu código e entender por que o a recursão nunca termina.

  2. Se tiver verificado que a recursão é implementado corretamente, você pode aumentar o tamanho da pilha, em ordem para permitir um maior número de invocações. Dependendo do Java Máquina Virtual (JVM) instalada, o tamanho por omissão da pilha de linhas poderá igual a 512KB ou 1MB . Você pode aumentar a pilha de linhas tamanho com a bandeira -Xss. Esta bandeira pode ser especificada através do configuração do projecto, ou através da linha de comandos. O formato do -Xss o argumento é: -Xss<size>[g|G|m|M|k|K]

 75
Author: Varun, 2018-06-07 10:31:58

Se tem uma função como:

int foo()
{
    // more stuff
    foo();
}
{[[2]} Então o foo () vai continuar a chamar-se a si mesmo, cada vez mais profundo, e quando o espaço usado para manter o controle das funções em que você está é preenchido, você obterá o erro de fluxo de pilha.
 61
Author: Khoth, 2008-10-18 08:31:36

O excesso de pilha significa exactamente isso: uma pilha transborda. Normalmente há uma pilha no programa que contém variáveis de âmbito local e endereços onde retornar quando a execução de uma rotina termina. Essa pilha tende a ser um intervalo de memória fixa em algum lugar na memória, portanto é limitado quanto pode conter valores.

Se a pilha estiver vazia, não poderá estourar, se o fizer, obterá um erro de fluxo de pilha.

Se a pilha estiver cheia não podes empurrar, se o fizeres vais ter erro de excesso de pilha.

Por isso, o estouro da pilha aparece onde você atribui demasiado para a pilha. Por exemplo, na recursão mencionada.

Algumas implementações otimizam algumas formas de recursões. Recursão da cauda em particular. As rotinas recursivas de cauda são formas de rotinas onde a chamada recursiva aparece como uma coisa final que a rotina faz. Essa chamada de rotina é simplesmente reduzida a um salto. Algumas implementações vão ao ponto de implementar suas próprias pilhas para recursão, portanto eles permitem que a recursão continue até que o sistema fique sem memória.

A coisa mais fácil que você poderia tentar seria aumentar o seu tamanho da pilha se você puder. Se você não pode fazer isso no entanto, a segunda melhor coisa seria olhar se há algo que claramente causa o excesso de pilha. Tente imprimindo algo antes e depois da chamada para a rotina. Isto ajuda-o a descobrir a rotina falhada.

 23
Author: Cheery, 2008-10-18 10:06:02

Um estouro de pilha é normalmente chamado por funções de nidificação chamadas demasiado profundamente (especialmente fácil ao usar recursão, isto é, uma função que se chama a si mesma) ou alocando uma grande quantidade de memória na pilha onde usar o heap seria mais apropriado.

 8
Author: Greg, 2008-10-18 08:19:15
Como disseste, tens de mostrar algum código. :-)

Um erro de excesso de pilha geralmente acontece quando a sua função chama ninho muito profundamente. Veja a Linha Stack Overflow Code Golf para alguns exemplos de como isso acontece (embora no caso dessa pergunta, as respostas intencionalmente causem overflow stack).

 6
Author: Chris Jester-Young, 2017-05-23 12:26:32

A causa mais comum dos transbordos da pilha é recursão excessivamente profunda ou infinita. Se este é o seu problema, este tutorial sobre a recursão Java pode ajudar a entender o problema.

 5
Author: splattne, 2008-10-18 08:43:31

StackOverflowError é para a pilha como OutOfMemoryError é para a pilha.

Chamadas recursivas sem limites resultam em espaço de pilha a ser usado.

O exemplo seguinte produz StackOverflowError:

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

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

StackOverflowError is avoidable if recursive calls are bounded to prevent the aggregate total of incomplete in-memory calls (in bytes) from Overing the stack size (in bytes).

 5
Author: Vikram, 2018-05-16 15:57:40

Aqui está um exemplo de um algoritmo recursivo para reverter uma lista única ligada. Em um laptop com o seguinte spec (memória 4G, CPU i5 2.3 GHz do núcleo Intel, Windows 7 de 64 bits), esta função vai correr em erro StackOverflow para uma lista de tamanho ligado perto de 10.000.

O que quero dizer é que devemos usar a recursividade judiciosamente, tendo sempre em conta a escala do sistema. Muitas vezes a recursão pode ser convertida para programa iterativo, que balança melhor. (Uma versão iterativa do mesmo algoritmo é dado na parte inferior da página, ele inverte uma lista de tamanho único de 1 milhão em 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);
}

Versão iterativa do mesmo 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);
}
 3
Author: Yiling, 2013-01-16 19:49:52

A StackOverflowError é um erro de execução em java.

É jogado quando a quantidade de memória da pilha de chamadas é atribuída pela JVM é excedida.

Um caso comum de um StackOverflowErrora ser lançado quando a pilha de chamadas excede por causa da recursão excessiva profunda ou infinita.

Exemplo:

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)

No caso acima pode ser evitado fazer mudanças programáticas. Mas se a lógica do programa corrigir e ainda ocorrer, então o tamanho da pilha precisa ser aumento.

 3
Author: Rahul Sah, 2018-05-16 16:03:36
Aqui está um exemplo.
public static void main(String[] args) {
    System.out.println(add5(1));
}

public static int add5(int a) {
    return add5(a) + 5;
}

Um StackOverflowError basicamente é quando você tenta fazer algo, que provavelmente se chama a si mesmo, e continua para o infinito (ou até que ele dá um StackOverflowError).

add5(a) vai chamar-se, e depois chamar-se novamente, e assim por diante.

 0
Author: John S., 2016-02-11 23:02:33

O termo "Stack overerful (overflow)" é frequentemente usado, mas um nome errado; os ataques não transbordam a pilha, mas sim os buffers na pilha.

[1]-- from lecture slides of Prof. Dr. Dieter Gollmann
 0
Author: Sergiu, 2018-07-24 19:29:42
Este é um caso típico de java.lang.StackOverflowError... O método é recursivamente chamar-se sem saída em 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();
        }
    }

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

Aqui está o código fonte de StackOverflowError no OpenJDK 7

 -1
Author: ZhiXingZhe - WangYuQi, 2018-04-02 12:41:59