2009-06-04 15 views
15

Come gestire StackOverflowError in Java?Come gestire StackOverflowError in Java?

+25

Blocco. Questo è quello che faccio sempre. –

+1

Si prega di inviare il codice che sta causando l'overflow dello stack. Evitare gli overflow dello stack sono quasi sempre migliori quindi provare a gestire l'eccezione. –

+8

Ah, l'ironia di quella domanda su un sito web con questo nome ... –

risposta

14

Non sono sicuro di cosa intendi con "handle".

Si può certamente prendere questo errore:

public class Example { 
    public static void endless() { 
     endless(); 
    } 

    public static void main(String args[]) { 
     try { 
      endless(); 
     } catch(StackOverflowError t) { 
      // more general: catch(Error t) 
      // anything: catch(Throwable t) 
      System.out.println("Caught "+t); 
      t.printStackTrace(); 
     } 
     System.out.println("After the error..."); 
    } 
} 

ma che è molto probabilmente una cattiva idea, a meno che non si sa esattamente cosa si sta facendo.

+3

Gestione dell'eccezione significa voler procedere ulteriormente dopo aver ignorato l'eccezione. Dal momento che si tratta di stackOverflowException, penso che non sia disponibile spazio su stack richiesto da java. Quindi, nel caso in cui come procedere ulteriormente? –

+4

Molto probabilmente hai un problema grave nel tuo programma. Valuta la traccia dello stack e controlla se trovi qualcosa di sospetto. È possibile continuare dopo aver catturato StackOverflowError perché il callstack è stato svuotato dall'errore generato ...Posso solo ripetermi, però: trova il vero bug che sta causando il problema! – Huxi

+4

Ankur, è un errore StachOverflowError, non StackOverflowException, con la differenza che il nome "ERRORE" indica che non dovrebbe essere catturato tramite try ... catch ma è necessario riscrivere il codice come suggerisco io e molti altri. – Avrom

17

Probabilmente hai qualche ricorsione infinita in corso.

I.e. un metodo che si chiama più e più

public void sillyMethod() 
{ 
    sillyMethod(); 
} 

One per gestire questa situazione è quello di risolvere il tuo codice in modo che la ricorsione termina invece di continuare per sempre.

+3

a volte l'infinita ricorsione si maschera molto bene. Guarda lo stacktrace all'interno del debugger dopo lo stackoverflow. –

+4

+1 per il nome del metodo. Inoltre: KnightsWhoSayNeet() – kevinarpe

1

Immagino che tu non possa - o almeno dipenda dal jvm che usi. Stack overflow significa che non hai spazio per memorizzare variabili locali e indirizzi di ritorno. Se il tuo jvm fa una qualche forma di compilazione, hai lo stackoverflow anche in jvm e questo significa che non puoi gestirlo o catturarlo. Il jvm deve terminare.

Potrebbe esserci un modo per creare un jvm che consente tale comportamento, ma sarebbe lento.

Non ho testato il comportamento con jvm, ma in .net non è possibile gestire lo stackoverflow. Anche provare a prendere non aiuterà. Poiché java e .net si basano sullo stesso concetto (macchine virtuali con jit), sospetto che java si comporterebbe allo stesso modo. La presenza di un'eccezione di stackoverflow in .NET suggerisce che potrebbero esserci alcuni vm che consentono al programma di catturarlo, il normale non lo fa.

0

La traccia di stack dovrebbe indicare la natura del problema. Ci dovrebbe essere un ciclo evidente quando si legge la traccia dello stack.

Se non si tratta di un bug, è necessario aggiungere un contatore o qualche altro meccanismo per arrestare la ricorsione prima che la ricorsione diventi così profonda da causare un overflow dello stack.

Un esempio di ciò potrebbe essere se si gestisce l'XML nidificato in un modello DOM con chiamate ricorsive e l'XML è nidificato in modo così profondo da causare uno stack overflow con le chiamate nidificate (improbabile, ma possibile). Questo dovrebbe essere nidificazione piuttosto profonda per causare un eccesso di stack però.

0

Come menzionato da molti in questa discussione, la causa comune di ciò è una chiamata al metodo ricorsivo che non termina. Laddove possibile, evita l'overflow dello stack e se questo nei test dovresti considerare questo nella maggior parte dei casi come un bug serio. In alcuni casi è possibile configurare le dimensioni dello stack di thread in Java per essere più grandi per gestire alcune circostanze (i set di dati di grandi dimensioni vengono gestiti nello storage di stack locale, lunghe chiamate ricorsive) ma ciò aumenterà l'ingombro di memoria complessivo che può causare problemi nel numero di thread disponibili nella VM. Generalmente, se ottieni questa eccezione, il thread e tutti i dati locali su questo thread dovrebbero essere considerati toast e non utilizzati (cioè sospetti e probabilmente corrotti).

13

Dare un'occhiata al numero When debugging a stack overflow, you want to focus on the repeating recursive part. Un estratto:

If you go hunting through your defect tracking database trying to see whether this is a known issue or not, a search for the top functions on the stack is unlikely to find anything interesting. That's because stack overflows tend to happen at a random point in the recursion; each stack overflow looks superficially different from every other one even if they are the same stack overflow.

Suppose you're singing the song Frère Jacques, except that you sing each verse a few tones higher than the previous one. Eventually, you will reach the top of your singing range, and precisely where that happens depends on where your vocal limit lines up against the melody. In the melody, the first three notes are each a new "record high" (i.e., the notes are higher than any other note sung so far), and new record highs appear in the three notes of the third measure, and a final record high in the second note of the fifth measure.

If the melody represented a program's stack usage, a stack overflow could possibly occur at any of those five locations in the program's execution. In other words, the same underlying runaway recursion (musically represented by an ever-higher rendition of the melody) can manifest itself in five different ways. The "recursion" in this analogy was rather quick, just eight bars before the loop repeated. In real life, the loop can be quite long, leading to dozens of potential points where the stack overflow can manifest itself.

If you are faced with a stack overflow, then, you want to ignore the top of the stack, since that's just focusing on the specific note that exceeded your vocal range. You really want to find the entire melody, since that's what's common to all the stack overflows with the same root cause.

+0

link molto utile - questo è quello che ho imparato a fare in caso di overflow dello stack. –

7

Si potrebbe voler vedere se l'opzione "-Xss" è supportata dalla propria JVM.In tal caso, potresti provare a impostarlo su un valore di 512k (il valore predefinito è 256k sotto Windows a 32 bit e Unix) e vedere se questo fa qualcosa (tranne che farti sedere più a lungo fino a StackOverflowException). Nota che questa è un'impostazione per-thread, quindi se hai un sacco di thread in esecuzione potresti anche voler aumentare le impostazioni dell'heap.

+1

+1, questa è l'unica risposta che risolve la causa principale del problema. Lavoro in un ambiente aziendale con centinaia di servizi, e molti di loro a intermittenza falliscono - e non usano la ricorsione, stanno solo facendo molto. I servizi sono solidi una volta aumentata la dimensione dello stack dal valore predefinito di 1 MB, a 4 MB o addirittura 16 MB. E quando si verifica un "StackOverflowError", non possiamo necessariamente fare affidamento su di esso per apparire nei log, può essere un assoluto ^% £ * 1 di un problema da rintracciare. – Contango

0

Semplice,

Guarda la traccia dello stack che lo StackOverflowError produce in modo da sapere dove nel codice si verifica e utilizzarlo per capire come riscrivere il codice in modo che non chiama se stessa ricorsivamente (la probabile causa del tuo errore) quindi non accadrà più.

StackOverflowErrors non è qualcosa che deve essere gestito tramite una clausola try ... catch ma indica un difetto di base nella logica del codice che deve essere corretto dall'utente.

0

java.lang.Error javadoc:

Un errore è una sottoclasse di Throwable che indica gravi problemi che un'applicazione ragionevole non dovrebbe cercare di catturare. La maggior parte di tali errori sono condizioni anormali. L'errore ThreadDeath, sebbene sia una condizione "normale", è anche una sottoclasse di Errore perché la maggior parte delle applicazioni non dovrebbe tentare di catturarla. Un metodo non è richiesto per dichiarare nella sua clausola di derivazione qualsiasi sottoclasse di Errore che potrebbe essere generata durante l'esecuzione del metodo ma non rilevata, poiché questi errori sono condizioni anomale che non dovrebbero mai verificarsi.

Quindi, no. Cerca di scoprire cosa c'è che non va nella logica del tuo codice. Questa eccezione si verifica molto spesso a causa della ricorsione infinita.

4

La risposta corretta è quella già indicata. Probabilmente a) hai un bug nel tuo codice che porta a una ricorsione infinita che di solito è abbastanza facile da diagnosticare e correggere, oppure b) avere un codice che può portare a ricchezioni molto profonde, ad esempio attraversando in modo ricorsivo un albero binario sbilanciato. In quest'ultima situazione, è necessario modificare il codice per non allocare le informazioni nello stack (vale a dire non recurse) ma per allocarlo invece nell'heap.

Ad esempio, per un traversal di albero non bilanciato, è possibile memorizzare i nodi che dovranno essere rivisti in una struttura di dati Stack. Per un traversal in ordine si circoscriverebbe i rami di sinistra spingendo ciascun nodo mentre lo si visitava fino a quando non si preme una foglia, che elaboreresti, quindi fai scoppiare un nodo fuori dalla cima dello stack, lo elabora, quindi riavvia il loop con il right child (semplicemente impostando la variabile loop sul nodo destro.) Questo userà una quantità costante di stack spostando tutto ciò che era nello stack nell'heap nella struttura dati dello Stack. L'heap è in genere molto più abbondante dello stack.

Come qualcosa che di solito è una pessima idea, ma è necessario nei casi in cui l'uso della memoria è estremamente limitato, è possibile utilizzare l'inversione del puntatore. In questa tecnica, codifichi lo stack nella struttura che stai attraversando, e riutilizzando i collegamenti che stai attraversando, puoi farlo senza memoria addizionale o significativamente meno. Usando l'esempio precedente, invece di spingere i nodi quando eseguiamo il ciclo, abbiamo solo bisogno di ricordare il nostro genitore immediato, e ad ogni iterazione, impostiamo il collegamento che abbiamo attraversato al genitore corrente e quindi il genitore corrente al nodo che stiamo lasciando. Quando arriviamo a una foglia, la processiamo, poi andiamo dai nostri genitori e poi abbiamo un enigma. Non sappiamo se correggere il ramo sinistro, elaborare questo nodo e continuare con il ramo destro, o correggere il ramo destro e andare al nostro genitore. Quindi abbiamo bisogno di allocare un bit in più di informazioni mentre procediamo con l'iterazione. Tipicamente, per le realizzazioni di basso livello di questa tecnica, quel bit verrà memorizzato nel puntatore stesso, senza che sia necessario aggiungere memoria e memoria costante. Questa non è un'opzione in Java, ma potrebbe essere possibile estirpare questo bit nei campi usati per altre cose.Nel peggiore dei casi, questa è ancora una riduzione di almeno 32 o 64 volte della quantità di memoria necessaria. Ovviamente, questo algoritmo è estremamente facile da sbagliare con risultati completamente confusi e farebbe aumentare il caos totale con la concorrenza. Quindi non vale quasi mai l'incubo di manutenzione, tranne dove allocare memoria è insostenibile. L'esempio tipico è un garbage collector in cui algoritmi come questo sono comuni.

Ciò di cui volevo davvero parlare, tuttavia, è quando è possibile gestire StackOverflowError. Vale a dire fornire l'eliminazione della coda su JVM. Un approccio è quello di usare lo stile trampolino in cui invece di eseguire una chiamata tail si restituisce un oggetto procedura null, o se si sta restituendo un valore si restituisce quello. [Nota: questo richiede alcuni mezzi per dire che una funzione restituisce A o B. In Java, probabilmente il modo più semplice per farlo è quello di restituire normalmente un tipo e lanciare l'altro come eccezione.] Quindi quando si chiama un metodo, si è necessario eseguire un ciclo while chiamando le procedure null (che restituiranno esse stesse una procedura nulla o un valore) finché non si ottiene un valore. Un ciclo infinito diventerà un ciclo while che costringe costantemente gli oggetti procedura a restituire oggetti procedura. I vantaggi dello stile trampolino sono che utilizza solo un fattore costante più stack di quello che usereste con un'implementazione che elimina correttamente tutte le chiamate tail, usa il normale stack Java per chiamate non tail, la traduzione è semplice, e cresce solo codice da un fattore costante (noioso). Lo svantaggio è che si assegna un oggetto a ogni chiamata di metodo (che diventerà immediatamente inutile) e il consumo di questi oggetti comporta un paio di chiamate indirette per coda.

La cosa ideale da fare sarebbe quella di non allocare mai quelle procedure nulle o qualsiasi altra cosa in primo luogo, che è esattamente ciò che l'eliminazione chiamata a coda compirebbe. Lavorando con ciò che fornisce Java, però, quello che potremmo fare è eseguire il codice normalmente e fare solo queste procedure null quando esauriamo lo stack. Ora allociamo ancora quei frame inutili, ma lo facciamo sullo stack piuttosto che sull'heap e li deallociamo alla rinfusa, inoltre, le nostre chiamate sono normali chiamate dirette in Java. Il modo più semplice per descrivere questa trasformazione è riscrivere prima tutti i metodi di istruzioni multi-call in metodi che hanno due istruzioni di chiamata, ad esempio fgh() {f(); g(); h(); } diventa fgh() {f(); GH(); } e gh() {g(); h(); }. Per semplicità, supporrò che tutti i metodi finiscano in una coda, che può essere organizzata semplicemente impacchettando il resto di un metodo in un metodo separato, anche se in pratica dovresti gestirli direttamente. Dopo queste trasformazioni abbiamo tre casi, o un metodo ha zero chiamate, nel qual caso non c'è niente da fare, o ha una chiamata (coda), nel qual caso lo avvolgeremo in un blocco try-catch nello stesso la chiamata di coda nel caso di due chiamate. Infine, può avere due chiamate, una chiamata non tail e una coda, nel qual caso applichiamo la seguente trasformazione illustrata dall'esempio (usando la notazione lambda di C# che potrebbe essere facilmente sostituita con una classe interna anonima con una certa crescita):

// top-level handler 
Action tlh(Action act) { 
    return() => { 
     while(true) { 
      try { act(); break; } catch(Bounce e) { tlh(() => e.run())(); } 
     } 
    } 
} 

gh() { 
    try { g(); } catch(Bounce e) { 
     throw new Bounce(tlh(() => { 
      e.run(); 
      try { h(); } catch(StackOverflowError e) { 
       throw new Bounce(tlh(() => h()); 
      } 
     }); 
    } 
    try { h(); } catch(StackOverflowError e) { 
     throw new Bounce(tlh(() => h())); 
    } 
} 

Il vantaggio principale qui è che se non viene generata alcuna eccezione, questo è lo stesso codice che abbiamo iniziato con solo alcuni gestori di eccezioni extra installati. Dal momento che le chiamate tail (la chiamata h()) non gestiscono l'eccezione Bounce, quell'eccezione le attraverserà spostando quelle (inutili) fotogrammi dallo stack. Le chiamate non tail prendono le eccezioni di Bounce e le rilancano con il codice rimanente aggiunto. Questo scaricherà lo stack fino al livello più alto, eliminando i telai di chiamata tail, ma ricordando i frame di chiamata non tail nella procedura nulla. Quando finalmente eseguiremo la procedura nell'eccezione Bounce al livello più alto, ricreeremo tutti i frame di chiamata non tail. A questo punto, se siamo di nuovo a corto di stack, quindi, dal momento che non reinstalliamo i gestori di StackOverflowError, verrà annullato come desiderato, dato che siamo davvero fuori dallo stack. Se otteniamo un po 'di più, verrà installato un nuovo StackOverflowError appropriato. Inoltre, se facciamo progressi, ma poi finiamo di nuovo fuori dallo stack, non c'è alcun vantaggio nel ricollocare i frame che abbiamo già svolto, quindi installiamo nuovi handler di primo livello in modo che lo stack possa essere slegato solo da loro.

Il problema più grande con questo approccio è che probabilmente vorrai chiamare normali metodi Java e potresti avere arbitrariamente poco spazio nello stack quando lo fai, quindi potrebbero avere abbastanza spazio per iniziare ma non finire e non puoi riprendili nel mezzo. Ci sono almeno due soluzioni a questo. Il primo è spedire tutto questo lavoro su un thread separato che avrà il proprio stack. Questo è piuttosto efficace e piuttosto semplice e non introdurrà alcuna concorrenza (a meno che non lo si voglia.) Un'altra opzione è semplicemente quella di scaricare di proposito lo stack prima di chiamare qualsiasi normale metodo Java semplicemente lanciando uno StackOverflowError immediatamente prima di loro. Se ancora si esaurisce lo spazio di stack quando si riprende, allora si è stati fregati per cominciare.

Una cosa simile può essere fatta anche per fare continuazioni just-in-time. Sfortunatamente, questa trasformazione non è davvero sopportabile da fare a mano in Java, ed è probabilmente borderline per linguaggi come C# o Scala. Quindi, le trasformazioni come questa tendono ad essere fatte da linguaggi che prendono di mira la JVM e non dalle persone.

-1
/* 
Using Throwable we can trap any know error in JAVA.. 
*/ 
public class TestRecur { 
    private int i = 0; 


    public static void main(String[] args) { 
     try { 
      new TestRecur().show(); 
     } catch (Throwable err) { 
      System.err.println("Error..."); 
     } 
    } 

    private void show() { 
     System.out.println("I = " + i++); 
     show(); 
    } 
} 

// comunque u può dare un'occhiata al link: http://marxsoftware.blogspot.in/2009/07/diagnosing-and-resolving.html per // capire i snipets codice che può generare l'errore

1

La maggior parte delle possibilità di ottenere StackOverflowError sono utilizzando [infinite/lunghe] ricorsioni in una funzione ricorsiva.

È possibile evitare la ricorsione della funzione modificando la progettazione dell'applicazione per utilizzare oggetti di dati impilabili. Esistono schemi di codifica per convertire i codici ricorsivi in ​​blocchi di codice iterativi. Date un'occhiata al di sotto answeres:

Così, si evita la memoria accatastamento da Java per le chiamate di funzione recessivi, utilizzando il proprio stack di dati.

0

in alcune occasioni, non è possibile catturare stackoverflowerror. ogni volta che ci provi, ne incontrerai uno nuovo. perché è java vm. è utile trovare blocchi di codice ricorsivi, come Andrew Bullock's said.

Problemi correlati