O que é um StackOverflowError?

439

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

Ziggy
fonte
O tamanho da pilha no java é pequeno. E algumas vezes, como muitas chamadas recursivas, você enfrenta esse problema. Você pode redesenhar seu código por loop. Você pode encontrar um padrão geral de design para fazê-lo neste URL: jndanial.com/73
JNDanial 19/09/18
Uma maneira não óbvia de obtê-lo: adicione a linha new Object() {{getClass().newInstance();}};a algum contexto estático (por exemplo, mainmétodo). Não funciona no contexto da instância (somente lançamentos InstantiationException).
John McClane

Respostas:

408

Parâmetros e variáveis ​​locais são alocados na pilha (com tipos de referência, o objeto permanece na pilha e uma variável na pilha faz referência a esse objeto na pilha). A pilha normalmente fica na extremidade superior do seu espaço de endereço e, conforme é usada, segue para a parte inferior do espaço de endereço (ou seja, para zero).

Seu processo também possui uma pilha , que fica na parte inferior do processo. À medida que você aloca memória, esse heap pode crescer na extremidade superior do seu espaço de endereço. Como você pode ver, existe um potencial para a pilha "colidir" com a pilha (um pouco como placas tectônicas !!!).

A causa comum para um estouro de pilha é uma chamada recursiva incorreta . Normalmente, isso é causado quando suas funções recursivas não têm a condição de terminação correta e, portanto, acaba se chamando para sempre. Ou, quando a condição de finalização é boa, pode ser causada pela necessidade de muitas chamadas recursivas antes de ser atendida.

No entanto, com a programação da GUI, é possível gerar recursão indireta . Por exemplo, seu aplicativo pode manipular mensagens de pintura e, ao processá-las, pode chamar uma função que faz com que o sistema envie outra mensagem de pintura. Aqui você não se chamou explicitamente, mas o OS / VM fez isso por você.

Para lidar com eles, você precisará examinar seu código. Se você possui funções que se chamam, verifique se possui uma condição de término. Se tiver, verifique se, ao chamar a função, você modificou pelo menos um dos argumentos; caso contrário, não haverá alteração visível para a função chamada recursivamente e a condição de término é inútil. Lembre-se também de que o espaço da pilha pode ficar sem memória antes de atingir uma condição de finalização válida, portanto, verifique se o método pode lidar com valores de entrada que exigem chamadas mais recursivas.

Se você não possui funções recursivas óbvias, verifique se está chamando alguma função de biblioteca que indiretamente faça com que sua função seja chamada (como o caso implícito acima).

Sean
fonte
1
Pôster original: ei, isso é ótimo. Portanto, a recursão é sempre responsável pelo excesso de pilha? Ou outras coisas também podem ser responsáveis ​​por eles? Infelizmente estou usando uma biblioteca ... mas não uma que eu entenda.
Ziggy
4
Ha ha ha, então aqui está: while (points <100) {addMouseListeners (); moveball (); checkforcollision (); pause (speed);} Uau, eu me sinto mal por não perceber que acabaria com uma pilha de ouvintes de mouse ... Obrigado pessoal!
Ziggy
4
Não, o excesso de pilha também pode vir de variáveis ​​muito grandes para serem alocadas na pilha se você procurar o artigo da Wikipedia em en.wikipedia.org/wiki/Stack_overflow .
JB King
8
Deve-se ressaltar que é quase impossível "manipular" um erro de estouro de pilha. Na maioria dos ambientes, para lidar com o erro, é necessário executar o código na pilha, o que é difícil se não houver mais espaço na pilha.
Hot Licks
3
@ King JB: Realmente não se aplica a Java, onde apenas tipos e referências primitivas são mantidas na pilha. Todo o material grande (matrizes e objetos) está na pilha.
jcsahnwaldt diz GoFundMonica 23/01
107

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

Variáveis ​​locais são armazenadas na pilha : insira a descrição da imagem aqui

Se você olhou para a imagem, deve entender como as coisas estão funcionando.

Quando uma chamada de função é chamada por um aplicativo Java, um quadro de pilha é alocado na pilha de chamadas. O quadro da pilha contém os parâmetros do método chamado, seus parâmetros locais e o endereço de retorno do método. O endereço de retorno indica o ponto de execução a partir do qual a execução do programa deve continuar após o retorno do método invocado. Se não houver espaço para um novo quadro de pilha, ele StackOverflowErrorserá lançado pela Java Virtual Machine (JVM).

O caso mais comum que pode esgotar a pilha de um aplicativo Java é a recursão. Na recursão, um método se chama durante sua execução. A recursão é considerada uma poderosa técnica de programação de uso geral, mas deve ser usada com cautela, 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, definimos um método recursivo, chamado recursivePrintque imprime um número inteiro e, em seguida, se chama, com o próximo número inteiro sucessivo como argumento. A recursão termina até que passemos 0como um parâmetro. No entanto, em nosso exemplo, passamos o parâmetro 1 e seus seguidores crescentes; consequentemente, a recursão nunca terminará.

Uma execução de amostra, usando o -Xss1Msinalizador que especifica o tamanho da pilha de encadeamentos igual a 1 MB, é 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 StackOverflowErrorserão lançados. Este exemplo é um exemplo muito bom de como a recursão pode causar problemas, se não for implementada com cuidado.

Como lidar com o StackOverflowError

  1. A solução mais simples é inspecionar cuidadosamente o rastreamento de pilha e detectar o padrão repetitivo dos números de linha. Esses números de linha indicam o código que está sendo chamado recursivamente. Depois de detectar essas linhas, você deve inspecionar cuidadosamente seu código e entender por que a recursão nunca termina.

  2. Se você verificou se a recursão foi implementada corretamente, é possível aumentar o tamanho da pilha, para permitir um número maior de chamadas. Dependendo da Java Virtual Machine (JVM) instalada, o tamanho padrão da pilha de encadeamentos pode ser igual a 512 KB ou 1 MB . Você pode aumentar o tamanho da pilha de threads usando o -Xsssinalizador. Esse sinalizador pode ser especificado pela configuração do projeto ou pela linha de comando. O formato do -Xssargumento é: -Xss<size>[g|G|m|M|k|K]

Varun
fonte
Parece haver um bug em algumas versões Java ao usar janelas onde o argumento -Xss só tem efeito sobre novos tópicos
goerlibe
65

Se você tem uma função como:

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

Então foo () continuará se chamando, ficando cada vez mais profundo, e quando o espaço usado para controlar as funções em que você estiver preenchido, você receberá o erro de estouro de pilha.

Khoth
fonte
12
Errado. Sua função é recursiva da cauda. A maioria dos idiomas compilados possui otimizações de recursão de cauda. Isso significa que a recursão se reduz em um loop simples e você nunca atingirá o estouro de pilha com esse trecho de código em alguns sistemas.
Animador
Animador, quais idiomas não funcionais suportam recursão de cauda?
Horseyguy 10/08/09
@banister e algumas implementações de javascript
Pacerier
@horseyguy Scala tem suporte para recursão de cauda.
Ajit K'sagar
Isso captura a essência do que pode criar um estouro de pilha. Agradável.
Pixel
24

O estouro de pilha significa exatamente isso: uma pilha está cheia. Geralmente, há uma pilha no programa que contém variáveis ​​e endereços de escopo local para onde retornar quando a execução de uma rotina terminar. Essa pilha tende a ser um intervalo fixo de memória em algum lugar da memória; portanto, é limitado o quanto ela pode conter valores.

Se a pilha estiver vazia, você não poderá aparecer, caso contrário, receberá um erro de estouro de pilha.

Se a pilha estiver cheia, você não poderá enviar por push, caso contrário, receberá um erro de estouro de pilha.

Portanto, o excesso de pilha aparece onde você aloca muito na 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 em que a chamada recursiva aparece como uma coisa final do que a rotina faz. Essa chamada de rotina é simplesmente reduzida a um salto.

Algumas implementações vão tão longe quanto implementam suas próprias pilhas para recursão, portanto, 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 tamanho da pilha, se puder. Se você não puder fazer isso, a segunda melhor coisa seria verificar se há algo que claramente causa o estouro da pilha. Experimente imprimindo algo antes e depois da chamada na rotina. Isso ajuda você a descobrir a rotina com falha.

Alegre
fonte
4
Existe algo como um estouro de pilha ?
Pacerier
5
Um fluxo insuficiente de pilha é possível no assembly (aparecendo mais do que você pressionou), embora em idiomas compilados seja quase impossível. Não tenho certeza, você pode encontrar uma implementação do alloca () de C que "suporte" tamanhos negativos.
Score_Und
2
O estouro de pilha significa exatamente isso: uma pilha está cheia. Normalmente há uma pilha no programa que contém variáveis de escopo local -> Não, cada segmento tem sua própria pilha que contém quadros de pilha para cada invocação de método que contém as variáveis locais ..
Koray Tugay
9

Um estouro de pilha geralmente é chamado aninhando chamadas de função muito profundamente (especialmente fácil ao usar recursão, ou seja, uma função que se chama) ou alocando uma grande quantidade de memória na pilha, onde o heap seria mais apropriado.

Greg
fonte
1
Ops, não vi a tag Java
Greg
Além disso, no pôster original aqui: o aninhamento funciona muito profundamente em quê? Outras funções? E: como alocar memória para a pilha ou pilha (desde que, você sabe, eu claramente fiz uma dessas coisas sem saber).
Ziggy
@Ziggy: Sim, se uma função chama outra função, que chama outra função, e assim por diante, após muitos níveis, seu programa terá um estouro de pilha. [continua]
Chris Jester-Young
[... continuação] Em Java, você não pode alocar memória diretamente da pilha (enquanto em C, você pode, e isso seria algo a ser observado), então é improvável que seja a causa. Em Java, todas as alocações diretas vêm do heap, usando "new".
Chris Jester-Young
@ ChrisJester-Young Não é verdade que, se eu tiver 100 variáveis ​​locais em um método, tudo isso vai para a pilha sem exceções?
Pacerier
7

Como você diz, você precisa mostrar algum código. :-)

Um erro de estouro de pilha geralmente ocorre quando suas chamadas de função aninham-se muito profundamente. Consulte o thread Golf do código de estouro de pilha para obter alguns exemplos de como isso acontece (embora, no caso dessa pergunta, as respostas intencionalmente causem estouro de pilha).

Chris Jester-Young
fonte
1
Eu gostaria totalmente de adicionar código, mas como não sei o que causa o estouro da pilha, não tenho certeza de qual código adicionar. adicionar todo o código seria coxo, não?
Ziggy
Seu projeto é de código aberto? Nesse caso, basta criar uma conta no Sourceforge ou no github e fazer upload de todo o seu código lá. :-)
Chris Jester-Young
isso soa como uma ótima idéia, mas sou tão noob que nem sei o que teria que enviar. Tipo, a biblioteca que estou importando classes que estou estendendo etc ... são todas desconhecidas para mim. Oh cara: tempos ruins.
Ziggy
5

A causa mais comum de estouros de pilha é recursão excessivamente profunda ou infinita . Se esse for o seu problema, este tutorial sobre Java Recursion pode ajudar a entender o problema.

splattne
fonte
5

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

Chamadas recursivas não ligadas resultam no uso do espaço da pilha.

O exemplo a seguir produz StackOverflowError:

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

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

StackOverflowError é evitável se chamadas recursivas estiverem limitadas para impedir que o total agregado de chamadas incompletas na memória (em bytes) exceda o tamanho da pilha (em bytes).

Vikram
fonte
3

Aqui está um exemplo de um algoritmo recursivo para reverter uma lista vinculada individualmente. Em um laptop com a seguinte especificação (memória 4G, CPU Intel Core i5 de 2,3 GHz, Windows 7 de 64 bits), essa função será executada no erro StackOverflow para uma lista vinculada de tamanho próximo a 10.000.

O que quero dizer é que devemos usar a recursão criteriosamente, sempre levando em consideração a escala do sistema. Freqüentemente, a recursão pode ser convertida em programa iterativo, que é melhor dimensionado. (Uma versão iterativa do mesmo algoritmo é fornecida na parte inferior da página, ela reverte uma lista isolada de tamanho 1 milhão em 9 milissegundos.)

    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);
}
Yiling
fonte
Eu acho que com a JVM, na verdade não importa quais são as especificações do seu laptop.
kevin
3

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

É acionada quando a quantidade de memória da pilha de chamadas alocada pela JVM é excedida.

Um caso comum de um StackOverflowErrorlançamento, é quando a pilha de chamadas excede devido a uma recursão profunda ou infinita excessiva.

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

Rastreio de pilha:

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, isso pode ser evitado fazendo alterações programáticas. Mas se a lógica do programa estiver correta e ainda ocorrer, o tamanho da pilha precisa ser aumentado.

Rahul Sah
fonte
0

Este é um caso típico de java.lang.StackOverflowError... O método está se chamando recursivamente, sem saída 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)

Aqui está o código fonte do StackOverflowErrorOpenJDK 7


fonte
0

Em uma crise, a situação Abaixo trará um erro de estouro de pilha.

public class Example3 {

public static void main(String[] args) {

    main(new String[1]);

}

}

Kaliappan
fonte
-1

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 e continua até o infinito (ou até que dê um StackOverflowError).

add5(a) chama a si próprio e depois chama a si mesmo novamente, e assim por diante.

John S.
fonte
-1

O termo "excedente de pilha (estouro)" é frequentemente usado, mas um nome impróprio; ataques não sobrecarregam a pilha, mas armazenam buffers na pilha.

- dos slides das palestras do Prof. Dr. Dieter Gollmann

Sergiu
fonte