Pergunta

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

Foi útil?

Solução

Parâmetros e variáveis ​​locais são alocados no pilha (com tipos de referência, o objeto reside no amontoar e uma variável na pilha faz referência a esse objeto na pilha).A pilha normalmente reside no superior final do seu espaço de endereço e à medida que é usado, ele se dirige para o fundo do espaço de endereço (ou seja,em direção a zero).

Seu processo também tem um amontoar, que mora no fundo final do seu processo.À medida que você aloca memória, esse heap pode crescer em direção à extremidade superior do seu espaço de endereço.Como você pode ver, existe um potencial para o heap "colidir" com a pilha (um pouco como placas tectônicas!!!).

A causa comum para um estouro de pilha é um chamada recursiva ruim.Normalmente, isso é causado quando suas funções recursivas não têm a condição de encerramento correta, então acabam se chamando para sempre.Ou quando a condição de encerramento é boa, isso pode ser causado pela necessidade de muitas chamadas recursivas antes de atendê-la.

Porém, com a programação GUI, é possível gerar recursão indireta.Por exemplo, seu aplicativo pode estar manipulando 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 autodenominam, verifique se possui uma condição de terminação.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 finalização será inútil.Lembre-se também de que seu espaço de pilha pode ficar sem memória antes de atingir uma condição de finalização válida, portanto, certifique-se de que seu método possa 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 fará com que sua função seja chamada (como o caso implícito acima).

Outras dicas

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

Variáveis ​​locais são armazenadas em pilha: enter image description here

Se você olhar a imagem, poderá entender como as coisas estão funcionando.

Quando uma chamada de função é invocada por um aplicativo Java, um quadro de pilha é alocado na pilha de chamadas.O stack frame 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 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 stack frame, o StackOverflowError é 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 invoca a si mesmo 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 jogando 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 recursivePrint que imprime um número inteiro e depois chama a si mesmo, com o próximo número inteiro sucessivo como argumento.A recursão termina até passarmos 0 como parâmetro.Porém, em nosso exemplo, passamos o parâmetro de 1 e seus seguidores crescentes, conseqüentemente a recursão nunca terminará.

Um exemplo de execução, usando o -Xss1M sinalizador que especifica o tamanho da pilha de threads como igual a 1 MB, é mostrado 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 ser diferentes, 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. A solução mais simples é inspecionar cuidadosamente o rastreamento da pilha e detectar o padrão de repetição dos números de linha.Estes números de linha Indique o código que está sendo chamado recursivamente.Depois de detectá-los linhas, você deve inspecionar cuidadosamente seu código e entender por que o a recursão nunca termina.

  2. Se você verificou que a recursão é implementado corretamente, você pode aumentar o tamanho da pilha, em para permitir um maior número de invocações.Dependendo do Java Máquina virtual (JVM) instalada, o tamanho padrão da pilha de threads pode igual a qualquer um 512 KB ou 1 MB.Você pode aumentar a pilha de threads tamanho usando o -Xss bandeira.Esse sinalizador pode ser especificado por meio do configuração do projeto, ou através da linha de comando.O formato do -Xss argumento é: -Xss<size>[g|G|m|M|k|K]

Se você tem uma função como:

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

Em seguida, o Foo () continuará ligando -se, ficando cada vez mais fundo e, quando o espaço usado para acompanhar quais funções você está preenchido, você recebe o erro de transbordamento da pilha.

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

Se a pilha estiver vazia, você não poderá estourá-la; se isso acontecer, você receberá um erro de estouro de pilha.

Se a pilha estiver cheia, você não poderá enviar push; se o fizer, receberá um erro de estouro de pilha.

Portanto, o estouro 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 de cauda em particular.Rotinas recursivas finais são formas de rotinas em que a chamada recursiva aparece como a última coisa que a rotina faz.Essa chamada de rotina é simplesmente reduzida a um salto.

Algumas implementações chegam ao ponto de implementar 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 do seu stack, se puder.Se você não puder fazer isso, a segunda melhor coisa seria verificar se há algo que causa claramente o estouro da pilha.Experimente imprimindo algo antes e depois da chamada na rotina.Isso ajuda você a descobrir a rotina com falha.

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

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

Um erro de transbordamento de pilha geralmente acontece quando sua função chama de nidada muito profundamente. Veja o Golfe de código de transbordamento de pilha Tópico para alguns exemplos de como isso acontece (embora, no caso dessa pergunta, as respostas intencionalmente causam transbordamento de pilha).

A causa mais comum de transbordamentos de pilha é recursão excessivamente profunda ou infinita. Se esse é o seu problema, Este tutorial sobre recursão Java poderia ajudar a entender o problema.

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

As chamadas recursivas ilimitadas resultam na usada espaço da pilha.

O exemplo seguinte produz StackOverflowError:

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

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

StackOverflowError é evitável se as chamadas recursivas forem delimitadas para impedir que o total agregado de chamadas na memória incompleta (em bytes) excedam o tamanho da pilha (em bytes).

Aqui está um exemplo de um algoritmo recursivo para reverter uma lista de vinculação individual. Em um laptop com as seguintes especificações (memória 4G, Intel Core i5 2.3GHz CPU, Windows 7 de 64 bits), essa função será exibida no Erro do 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. Muitas vezes, a recursão pode ser convertida ao programa iterativo, que é melhor. (Uma versão iterativa do mesmo algoritmo é dada na parte inferior da página, ele reverte uma lista de tamanho 1 de tamanho ligado 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);
}

UMA StackOverflowError é um erro de tempo de execução em Java.

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

Um caso comum de um StackOverflowError Ser jogado, é quando a pilha de chamadas excede devido à 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");
    }
}

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

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 para o Infinity (ou até dar um StackOverflowerRor).

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

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

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 de StackOverflowError no OpenJdk 7

O termo "excesso de pilha (transbordamento)" é frequentemente usado, mas um nome impróprio; Os ataques não transbordam da pilha, mas buffers na pilha.

- de slides de palestras de Dr. Dieter Gollmann

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top