Domanda

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

È stato utile?

Soluzione

I parametri e le variabili locali sono allocati sullo stack (con i tipi di riferimento, l'oggetto vive sullo heap e una variabile nello stack fa riferimento a quell'oggetto sull'heap) . Lo stack di solito vive all'estremità superiore dello spazio degli indirizzi e quando viene utilizzato si dirige verso il fondo dello spazio degli indirizzi (ovvero verso zero).

Il tuo processo ha anche un heap , che si trova nella parte in basso 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 & Quot; scontrarsi & Quot; 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 soddisfarle.

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

Altri suggerimenti

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

Le variabili locali sono archiviate 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, il StackOverflowError viene lanciato dalla Java Virtual Machine (JVM).

Il caso più comune che può esaurire un'applicazione Java & # 8217; s stack è la ricorsione. In ricorsione, un metodo si richiama durante la sua esecuzione. La ricorsione è considerata una potente tecnica di programmazione generica, ma deve essere utilizzata con cautela per evitare recursivePrint.

Di seguito è mostrato un esempio di lancio di 0:

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 -Xss1M che 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 -Xss come parametro. Tuttavia, nel nostro esempio, abbiamo passato il parametro da 1 e dai suoi follower in aumento, di conseguenza la ricorsione non si chiuderà mai.

Un'esecuzione di esempio, usando il flag -Xss<size>[g|G|m|M|k|K] che specifica la dimensione dello stack di thread uguale a 1 MB, è mostrata di seguito:

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 di JVM & # 8217, i risultati possono differire, ma alla fine deve essere lanciato <=>. 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 ripetuto dei numeri di riga. Questi numeri di riga indica il codice richiamato in modo ricorsivo. Una volta rilevati questi righe, è necessario controllare attentamente il codice e capire perché la ricorsione non termina mai.

  2. Se hai verificato che la ricorsione     è implementato correttamente, puoi aumentare la dimensione dello stack & # 8217; in     per consentire un numero maggiore di invocazioni. A seconda di Java     Macchina virtuale (JVM) installata, la dimensione dello stack di thread predefinita potrebbe     uguale a 512 KB o 1 MB . È possibile aumentare la pila di thread     dimensioni utilizzando il flag <=>. Questo flag può essere specificato tramite     progetto & # 8217; s configurazione, o tramite la riga di comando. Il formato del file     L'argomento <=> è:     <=>

Se hai una funzione come:

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

Quindi foo () continuerà a chiamare se stesso, 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.

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. Lo 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 allocate troppo nello stack. Ad esempio, nella menzionata 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 al punto di implementare 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.

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'uso dell'heap sarebbe più appropriato.

Come dici tu, devi mostrare del codice. : -)

Un errore di overflow dello stack si verifica in genere quando le chiamate di funzione vengono annidate troppo in profondità. Vedi il thread Stack Overflow Code Golf per alcuni esempi di come ciò avvenga (sebbene nella caso di tale domanda, le risposte causano intenzionalmente lo overflow dello stack).

La causa più comune di overflow dello stack è ricorsione eccessivamente profonda o infinita . Se questo è il tuo problema, questa esercitazione su Java Recursion potrebbe aiutare a capire il problema.

StackOverflowError è nello stack come OutOfMemoryError è nell'heap.

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

L'esempio seguente produce <=>:

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

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

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

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 con giudizio, 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 è riportata 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);
}

Un 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 lancio di <=> è quando lo stack di chiamate supera a causa di un'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 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.

Ecco un esempio

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

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

Un StackOverflowError è fondamentalmente quando provi a fare qualcosa, che molto probabilmente si chiama da solo, e continua all'infinito (o fino a quando non dà un StackOverflowError).

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

Questo è un tipico caso di java.lang.StackOverflowError ... Il metodo si chiama ricorsivamente se stesso senza uscita in 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)

Qui è il codice sorgente di StackOverflowError in OpenJDK 7

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

- dalle diapositive delle lezioni di Prof. Dr. Dieter Gollmann

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top