Che cos'è StackOverflowError?


439

Che cos'è un StackOverflowError, cosa lo provoca e come devo gestirli?


Le dimensioni dello stack in Java sono piccole. E alcune volte, come molte chiamate ricorsive, affronti questo problema. Puoi riprogettare il tuo codice in loop. Puoi trovare un modello di progettazione generale per farlo in questo URL: jndanial.com/73
JNDanial

Un modo non ovvio per ottenerlo: aggiungere la linea new Object() {{getClass().newInstance();}};ad un contesto statico (ad esempio mainmetodo). Non funziona dal contesto dell'istanza (solo proiezioni InstantiationException).
John McClane,

Risposte:


408

I parametri e le variabili locali sono allocati nello stack (con i tipi di riferimento, l'oggetto vive nell'heap e una variabile nello stack fa riferimento a quell'oggetto nell'heap). Lo stack in genere vive all'estremità superiore dello spazio degli indirizzi e quando viene utilizzato si dirige verso la parte inferiore dello spazio degli indirizzi (ovvero verso lo zero).

Il tuo processo ha anche un mucchio , che vive nella parte inferiore del processo. Quando si alloca memoria, questo heap può crescere verso l'estremità superiore dello spazio degli indirizzi. Come puoi vedere, c'è un potenziale per l'heap di "scontrarsi" con lo stack (un po 'come le placche tettoniche !!!).

La causa comune di un overflow dello stack è una cattiva chiamata ricorsiva . In genere, ciò si verifica quando le funzioni ricorsive non hanno la condizione di terminazione corretta, quindi finisce per chiamarsi per sempre. O quando la condizione di terminazione va bene, può essere causata richiedendo troppe chiamate ricorsive prima di soddisfarla.

Tuttavia, con la programmazione della GUI, è possibile generare ricorsione indiretta . Ad esempio, l'app potrebbe gestire i messaggi di disegno e, durante l'elaborazione, potrebbe chiamare una funzione che fa sì che il sistema invii un altro messaggio di disegno. Qui non ti sei esplicitamente chiamato, ma il sistema operativo / VM lo ha fatto per te.

Per gestirli, devi esaminare il tuo codice. Se hai funzioni che si richiamano, verifica di avere una condizione di chiusura. Se sì, controlla che quando chiami la funzione hai almeno modificato uno degli argomenti, altrimenti non ci saranno cambiamenti visibili per la funzione chiamata ricorsivamente e la condizione finale è inutile. Ricorda inoltre che lo spazio dello stack può esaurire la memoria prima di raggiungere una condizione di terminazione valida, quindi assicurati che il tuo metodo sia in grado di gestire i valori di input che richiedono più chiamate ricorsive.

Se non hai ovvie funzioni ricorsive, controlla se stai chiamando funzioni di libreria che indirettamente causeranno la chiamata della tua funzione (come il caso implicito sopra).


1
Poster originale: ehi, questo è fantastico. Quindi la ricorsione è sempre responsabile degli overflow dello stack? O anche altre cose possono essere responsabili di loro? Sfortunatamente sto usando una libreria ... ma non una che capisco.
Ziggy,

4
Ah ah ah, eccolo qui: while (punti <100) {addMouseListeners (); MoveBall (); checkforcollision (); pause (speed);} Wow, mi sento zoppo per non rendermi conto che sarei finito con un mucchio di ascoltatori di mouse ... Grazie ragazzi!
Ziggy,

4
No, gli overflow dello stack possono anche provenire da variabili troppo grandi per essere allocate nello stack se si cerca l'articolo di Wikipedia su di esso su en.wikipedia.org/wiki/Stack_overflow .
JB King,

8
Va sottolineato che è quasi impossibile "gestire" un errore di overflow dello stack. Nella maggior parte degli ambienti, per gestire l'errore è necessario eseguire il codice nello stack, il che è difficile se non c'è più spazio nello stack.
Hot Licks

3
@JB King: in realtà non si applica a Java, dove solo i tipi e i riferimenti primitivi sono mantenuti nello stack. Tutta la roba grande (array e oggetti) è sul mucchio.
jcsahnwaldt dice GoFundMonica il

107

Per descriverlo, dobbiamo prima capire come sono archiviate le variabili e gli oggetti locali .

Le variabili locali sono memorizzate nello stack : inserisci qui la descrizione dell'immagine

Se guardassi l'immagine dovresti essere in grado di capire come funzionano le cose.

Quando una chiamata di funzione viene invocata da un'applicazione Java, uno stack frame viene allocato nello stack di chiamate. Il frame dello stack contiene i parametri del metodo richiamato, i suoi parametri locali e l'indirizzo di ritorno del metodo. L'indirizzo di ritorno indica il punto di esecuzione dal quale, l'esecuzione del programma deve continuare dopo il ritorno del metodo invocato. Se non c'è spazio per un nuovo frame dello stack, StackOverflowErrorviene generato da Java Virtual Machine (JVM).

Il caso più comune che può esaurire lo stack di un'applicazione Java è la ricorsione. In ricorsione, un metodo si richiama durante la sua esecuzione. La ricorsione è considerata una potente tecnica di programmazione generale ma deve essere usata con cautela, per evitare StackOverflowError.

Un esempio di lancio di a StackOverflowErrorè mostrato di seguito:

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

In questo esempio, definiamo un metodo ricorsivo, chiamato recursivePrintche stampa un numero intero e quindi, chiama se stesso, con il numero intero successivo successivo come argomento. La ricorsione termina fino a quando non passiamo 0come parametro. Tuttavia, nel nostro esempio, abbiamo passato il parametro da 1 e dai suoi follower in aumento, di conseguenza, la ricorsione non si interromperà mai.

Di -Xss1Mseguito viene mostrata un'esecuzione di esempio, utilizzando il flag che specifica la dimensione dello stack di thread pari 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)
        ...

A seconda della configurazione iniziale della JVM, i risultati possono differire, ma alla fine StackOverflowErrordevono essere lanciati. Questo esempio è un ottimo esempio di come la ricorsione può causare problemi, se non implementata con cautela.

Come gestire StackOverflowError

  1. La soluzione più semplice è ispezionare attentamente la traccia dello stack e rilevare lo schema ripetitivo dei numeri di riga. Questi numeri di riga indicano il codice chiamato in modo ricorsivo. Una volta rilevate queste righe, è necessario ispezionare attentamente il codice e capire perché la ricorsione non si interrompe mai.

  2. Se hai verificato che la ricorsione è implementata correttamente, puoi aumentare le dimensioni dello stack, al fine di consentire un numero maggiore di invocazioni. A seconda della Java Virtual Machine (JVM) installata, la dimensione dello stack dei thread predefinita può essere pari a 512 KB o 1 MB . Puoi aumentare le dimensioni dello stack del thread usando il -Xssflag. Questo flag può essere specificato tramite la configurazione del progetto o tramite la riga di comando. Il formato -Xssdell'argomento è: -Xss<size>[g|G|m|M|k|K]


Sembra che ci sia un bug in alcune versioni Java quando si utilizza Windows in cui l'argomento -Xss ha effetto solo sui nuovi thread
goerlibe

65

Se hai una funzione come:

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

Quindi foo () continuerà a chiamarsi, sempre più in profondità, e quando lo spazio utilizzato per tenere traccia delle funzioni in cui ti trovi viene riempito, viene visualizzato l'errore di overflow dello stack.


12
Sbagliato. La tua funzione è ricorsiva alla coda. La maggior parte dei linguaggi compilati ha ottimizzazioni ricorsive della coda. Ciò significa che la ricorsione si riduce in un semplice ciclo e su alcuni sistemi non verrà mai raggiunto lo stack overflow con questo pezzo di codice.
Allegro

Allegri, quali lingue non funzionali supportano la ricorsione della coda?
Horseyguy,

@banister e alcune implementazioni di javascript
Pacerier

@horseyguy Scala ha il supporto per la ricorsione della coda.
Ajit K'sagar,

Questo cattura l'essenza di ciò che può creare un overflow dello stack. Bello.
Pixel

24

Stack overflow significa esattamente questo: uno stack trabocca. Di solito c'è uno stack nel programma che contiene variabili e indirizzi di ambito locale dove restituire quando termina l'esecuzione di una routine. Tale stack tende ad essere un intervallo di memoria fisso da qualche parte nella memoria, quindi è limitato quanto può contenere valori.

Se lo stack è vuoto non è possibile pop, in caso contrario si verificherà un errore di underflow dello stack.

Se lo stack è pieno non è possibile inviare, in caso contrario si verificherà un errore di overflow dello stack.

Quindi lo overflow dello stack appare dove si alloca troppo nello stack. Ad esempio, nella citata ricorsione.

Alcune implementazioni ottimizzano alcune forme di ricorsione. Ricorsione della coda in particolare. Le routine ricorsive di coda sono una forma di routine in cui la chiamata ricorsiva appare come ultima cosa che fa la routine. Tale chiamata di routine viene semplicemente ridotta in un salto.

Alcune implementazioni arrivano fino a quando implementano le proprie pile per la ricorsione, quindi consentono alla ricorsione di continuare fino a quando il sistema esaurisce la memoria.

La cosa più semplice che potresti provare sarebbe di aumentare le dimensioni dello stack se puoi. Se non riesci a farlo, la seconda cosa migliore sarebbe guardare se c'è qualcosa che causa chiaramente lo overflow dello stack. Provalo stampando qualcosa prima e dopo la chiamata nella routine. Questo ti aiuta a scoprire la routine fallita.


4
Esiste qualcosa come uno stack underflow ?
Pacerier,

5
È possibile un underflow dello stack nell'assembly (spuntando più di quanto si è spinto), sebbene nei linguaggi compilati sarebbe quasi impossibile. Non sono sicuro, potresti essere in grado di trovare un'implementazione di alloca () di C che "supporta" dimensioni negative.
Score_Under

2
Stack overflow significa esattamente questo: uno stack trabocca. Di solito c'è uno stack nel programma che contiene variabili di ambito locale -> No, ogni thread ha il proprio stack che contiene frame di stack per ogni invocazione di metodo che contiene variabili locali ..
Koray Tugay,

9

Un overflow dello stack viene generalmente chiamato annidando le chiamate di funzione in modo troppo profondo (particolarmente semplice quando si utilizza la ricorsione, ovvero una funzione che chiama se stesso) o allocando una grande quantità di memoria nello stack in cui l'utilizzo dell'heap sarebbe più appropriato.


1
Oops, non ho visto il tag Java
Greg

Inoltre, dal poster originale qui: la nidificazione funziona troppo profondamente in cosa? Altre funzioni? E: come si alloca memoria allo stack o heap (dato che, sai, ho chiaramente fatto una di queste cose senza saperlo).
Ziggy,

@Ziggy: Sì, se una funzione chiama un'altra funzione, che chiama ancora un'altra funzione, e così via, dopo molti livelli, il tuo programma avrà uno stack overflow. [continua]
Chris Jester-Young,

[... continua] In Java, non è possibile allocare direttamente la memoria dallo stack (mentre in C, è possibile, e questo sarebbe quindi qualcosa da tenere d'occhio), quindi è improbabile che sia la causa. In Java, tutte le allocazioni dirette provengono dall'heap, usando "new".
Chris Jester-Young,

@ ChrisJester-Young Non è vero che se ho 100 variabili locali in un metodo, tutto va nello stack senza eccezioni?
Pacerier,

7

Come dici tu, devi mostrare un po 'di codice. :-)

Un errore di overflow dello stack si verifica in genere quando le chiamate di funzione annidano troppo in profondità. Vedi il thread Golf del codice di overflow dello stack per alcuni esempi di come ciò accade (anche se nel caso di quella domanda, le risposte causano intenzionalmente lo overflow dello stack).


1
Vorrei assolutamente aggiungere il codice, ma poiché non so quali sono le cause degli overflow dello stack non sono sicuro di quale codice aggiungere. aggiungendo tutto il codice sarebbe zoppo, no?
Ziggy,

Il tuo progetto è open source? In tal caso, crea un account Sourceforge o github e carica lì tutto il codice. :-)
Chris Jester-Young,

sembra un'ottima idea, ma sono così indifferente che non so nemmeno cosa avrei dovuto caricare. Ad esempio, la libreria che sto importando classi che sto estendendo ecc ... sono tutte sconosciute per me. Oh amico: tempi brutti.
Ziggy


5

StackOverflowErrorè nello stack come OutOfMemoryErrorè nell'heap.

Le chiamate ricorsive illimitate comportano l'esaurimento dello spazio dello stack.

L'esempio seguente produce StackOverflowError:

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

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

StackOverflowError è evitabile se le chiamate ricorsive sono limitate per evitare che il totale aggregato delle chiamate in memoria incomplete (in byte) superi la dimensione dello stack (in byte).


3

Ecco un esempio di algoritmo ricorsivo per invertire un elenco collegato singolarmente. Su un laptop con le seguenti specifiche (memoria 4G, CPU Intel Core i5 2.3GHz, Windows 7 a 64 bit), questa funzione verrà eseguita nell'errore StackOverflow per un elenco collegato di dimensioni vicine a 10.000.

Il mio punto è che dovremmo usare la ricorsione in modo giudizioso, tenendo sempre conto delle dimensioni del sistema. Spesso la ricorsione può essere convertita in programma iterativo, che si ridimensiona meglio. (Una versione iterativa dello stesso algoritmo viene fornita in fondo alla pagina, inverte un elenco singolarmente collegato di dimensioni 1 milione in 9 millisecondi.)

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

Versione iterativa dello stesso 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);
}

Penso che con JVM, in realtà non importa quali siano le specifiche del tuo laptop.
Kevin

3

A StackOverflowErrorè un errore di runtime in Java.

Viene generato quando viene superata la quantità di memoria dello stack di chiamate allocata da JVM.

Un caso comune di StackOverflowErroressere lanciato, è quando lo stack di chiamate supera a causa di eccessiva ricorsione profonda o infinita.

Esempio:

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

Traccia dello stack:

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)

Nel caso precedente, può essere evitato apportando modifiche programmatiche. Ma se la logica del programma è corretta e si verifica ancora, è necessario aumentare le dimensioni dello stack.


0

Questo è un tipico caso di java.lang.StackOverflowError... Il metodo si chiama ricorsivamente senza uscire doubleValue(),floatValue() ecc

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

Risultato

    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)

Ecco il codice sorgente di StackOverflowErrorin OpenJDK 7


0

In una situazione di crisi, la situazione di sotto porterà ad un errore di overflow dello stack.

public class Example3 {

public static void main(String[] args) {

    main(new String[1]);

}

}


-1

Ecco un esempio

public static void main(String[] args) {
    System.out.println(add5(1));
}

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

Uno StackOverflowError è fondamentalmente quando si tenta di fare qualcosa, che molto probabilmente si chiama da solo, e continua all'infinito (o fino a quando non si ottiene uno StackOverflowError).

add5(a) chiamerà se stesso, quindi chiamerà di nuovo se stesso e così via.


-1

Il termine "stack overrun (overflow)" è spesso usato ma un termine improprio; gli attacchi non traboccano dallo stack ma dai buffer nello stack.

- da diapositive del Prof. Dr. Dieter Gollmann

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.