Cos’è un StackOverflowError?

Cos’è un StackOverflowError , cosa lo causa e come dovrei gestirli?

I parametri e le variabili locali sono allocate nello stack (con i tipi di riferimento l’object vive sull’heap e una variabile fa riferimento a quell’object). Lo stack in genere si trova all’estremità superiore dello spazio degli indirizzi e, una volta esaurito, si dirige verso il fondo dello spazio degli indirizzi (cioè verso lo zero).

Il processo ha anche un mucchio , che vive nella parte inferiore del processo. Mentre assegni la memoria, questo heap può crescere verso l’estremità superiore dello spazio degli indirizzi. Come puoi vedere, c’è un potenziale per l’heap di “collidere” con lo stack (un po ‘come le placche tettoniche !!!).

La causa comune di un overflow dello stack è una chiamata ricorsiva non valida . Tipicamente, questo è causato quando le tue funzioni ricorsive non hanno la condizione di terminazione corretta, quindi finisce per chiamarsi per sempre.

Tuttavia, con la programmazione della GUI, è ansible generare una ricorsione indiretta . Ad esempio, la tua 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 chiamato esplicitamente, ma l’OS / VM lo ha fatto per te.

Per affrontarli, dovrai esaminare il tuo codice. Se hai funzioni che si chiamano, controlla che tu abbia una condizione di chiusura. Se lo hai, controlla che quando chiami la funzione hai almeno modificato uno degli argomenti, altrimenti non ci sarà alcun cambiamento visibile per la funzione chiamata ricorsivamente e la condizione di terminazione è inutile.

Se non hai evidenti funzioni ricorsive, controlla se stai chiamando qualsiasi funzione della libreria che indirettamente causerà il richiamo della funzione (come nel caso implicito sopra).

Per descriverlo, prima cerchiamo di capire come vengono memorizzate le variabili e gli oggetti locali .

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

Se hai guardato l’immagine dovresti essere in grado di capire come stanno andando le cose.

Quando una chiamata di funzione viene richiamata 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 da cui l’esecuzione del programma deve continuare dopo il ritorno del metodo richiamato. Se non c’è spazio per un nuovo stack frame, allora StackOverflowError viene lanciato 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 generica, ma deve essere usata con caucanvas, per evitare StackOverflowError .

Un esempio di lancio di 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 recursivePrint che stampa un intero e quindi, chiama se stesso, con il successivo intero successivo come argomento. La ricorsione termina finché non passiamo a 0 come parametro. Tuttavia, nel nostro esempio, abbiamo passato il parametro da 1 e i suoi follower crescenti, di conseguenza la ricorsione non terminerà mai.

Di seguito viene mostrata un’esecuzione di esempio, utilizzando il flag -Xss1M che specifica la dimensione della pila di thread uguale 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 potrebbero essere diversi, ma alla fine verrà lanciato l’ StackOverflowError . Questo esempio è un ottimo esempio di come la ricorsione può causare problemi, se non implementata con caucanvas.

Come gestire StackOverflowError

  1. La soluzione più semplice è ispezionare attentamente la traccia dello stack e rilevare il modello ripetuto dei numeri di riga. Questi numeri di riga indicano che il codice viene chiamato in modo ricorsivo. Una volta rilevate queste righe, è necessario esaminare attentamente il codice e capire perché la ricorsione non termina mai.

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

Se hai una funzione come:

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

Quindi foo () continuerà a chiamare se stesso, a diventare sempre più profondo e quando lo spazio utilizzato per tenere traccia di quali funzioni si sono riempite, si ottiene l’errore di overflow dello stack.

Overflow dello stack significa esattamente questo: uno stack trabocca. Di solito c’è una pila nel programma che contiene variabili e indirizzi dell’ambito locale dove tornare quando termina l’esecuzione di una routine. Quella pila tende ad essere un intervallo di memoria fisso da qualche parte nella memoria, quindi è limitato a quanto può contenere valori.

Se lo stack è vuoto non puoi pop, se lo fai avrai errore di underflow dello stack.

Se lo stack è pieno non puoi spingere, se lo fai otterrai un errore di overflow dello stack.

Quindi viene visualizzato overflow dello stack in cui viene allocata una quantità eccessiva nello stack. Ad esempio, nella ricorrenza menzionata.

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

Alcune implementazioni arrivano a implementare i propri stack per la ricorsione, pertanto consentono alla ricorsione di continuare finché il sistema non esaurisce la memoria.

La cosa più semplice da provare sarebbe quella di aumentare le dimensioni dello stack, se ansible. Se non riesci a farlo, la seconda cosa migliore sarebbe vedere se c’è qualcosa che fa chiaramente traboccare lo stack. Provalo stampando qualcosa prima e dopo la chiamata in routine. Questo ti aiuta a scoprire la routine fallita.

Un overflow dello stack viene in genere chiamato da chiamate di funzioni di nidificazione troppo profonde (particolarmente facile quando si utilizza la ricorsione, ovvero una funzione che chiama se stessa) o allocando una grande quantità di memoria nello stack in cui l’utilizzo 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 profondamente. Vedere il thread Golf Code Overflow dello stack per alcuni esempi di come ciò accade (sebbene nel caso di quella domanda, le risposte causino intenzionalmente un overflow dello stack).

StackOverflowError è nello stack poiché OutOfMemoryError trova nell’heap.

Le chiamate ricorsive illimitate portano allo esaurimento dello spazio di 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 impedire il totale complessivo delle chiamate in memoria incomplete (in byte) dal superamento della dimensione dello stack (in byte).

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

Ecco un esempio di un algoritmo ricorsivo per invertire una lista concatenata. 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 prossime a 10.000.

Il mio punto è che dovremmo usare la ricorsione in modo giudizioso, tenendo sempre conto della scala del sistema. Spesso la ricorsione può essere convertita in un programma iterativo, che si adatta meglio. (Una versione iterativa dello stesso algoritmo viene fornita nella parte inferiore della pagina, inverte un elenco collegato singolarmente di dimensione 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 lanciato quando viene superata la quantità di memoria dello stack delle chiamate allocata da JVM.

Un caso comune di un object StackOverflowError viene generato 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 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 di fare cambiamenti programmatici. Ma se la logica del programma è corretta e continua a verificarsi, è 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 si tenta di fare qualcosa, che molto probabilmente si chiama, e va avanti all’infinito (o finché non dà un StackOverflowError).

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

Il termine “stack overrun (overflow)” è spesso usato ma un termine improprio; gli attacchi non sovraccaricano lo stack ma i buffer nello stack.

– dalle diapositive del Prof. Dr. Dieter Gollmann

Questo è un tipico caso di java.lang.StackOverflowError … Il metodo si chiama in modo ricorsivo senza uscita in doubleValue() , floatValue() , ecc.

Rational.java

  public class Rational extends Number implements Comparable { 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 > 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 StackOverflowError in OpenJDK 7