Cattura java.lang.OutOfMemoryError?

La documentazione per java.lang.Error dice:

Un errore è una sottoclass di Throwable che indica i problemi seri che un’applicazione ragionevole non dovrebbe provare a catturare

Ma poiché java.lang.Error è una sottoclass di java.lang.Throwable , posso prendere questo tipo di Throwable.

Capisco perché non è una buona idea cogliere questo tipo di eccezione. Per quanto ho capito, se decidessimo di prenderlo, il gestore delle catture non dovrebbe allocare memoria da solo. Altrimenti OutOfMemoryError verrà nuovamente generato.

Quindi, la mia domanda è:

  1. C’è qualche vero scenario di parole quando catturare java.lang.OutOfMemoryError può essere una buona idea?
  2. Se decidiamo di catturare java.lang.OutOfMemoryError , come possiamo essere certi che catch handler non assegni memoria da solo (strumenti o best practice)?

Sono d’accordo e in disaccordo con la maggior parte delle risposte qui.

Ci sono una serie di scenari in cui potresti voler catturare un OutOfMemoryError e nella mia esperienza (su Windows e Solaris JVM), solo molto raramente OutOfMemoryError è il death-knell di una JVM.

C’è solo una buona ragione per catturare un OutOfMemoryError e questo è chiudere con grazia, liberando in modo pulito le risorse e registrando il motivo dell’errore meglio che puoi (se è ancora ansible farlo).

In generale, l’ OutOfMemoryError verifica a causa di un’allocazione di memoria di blocco che non può essere soddisfatta con le restanti risorse dell’heap.

Quando viene generato l’ Error l’heap contiene la stessa quantità di oggetti allocati rispetto a prima dell’allocazione non riuscita e ora è il momento di eliminare i riferimenti agli oggetti di runtime per liberare ancora più memoria che potrebbe essere richiesta per la pulizia. In questi casi, potrebbe anche essere ansible continuare, ma sarebbe sicuramente una ctriggers idea, poiché non si può mai essere sicuri al 100% che la JVM sia in uno stato riparabile.

Dimostrazione che OutOfMemoryError non significa che la JVM abbia esaurito la memoria nel blocco catch:

 private static final int MEGABYTE = (1024*1024); public static void runOutOfMemory() { MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean(); for (int i=1; i <= 100; i++) { try { byte[] bytes = new byte[MEGABYTE*500]; } catch (Exception e) { e.printStackTrace(); } catch (OutOfMemoryError e) { MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage(); long maxMemory = heapUsage.getMax() / MEGABYTE; long usedMemory = heapUsage.getUsed() / MEGABYTE; System.out.println(i+ " : Memory Use :" + usedMemory + "M/" + maxMemory + "M"); } } } 

Uscita di questo codice:

 1 : Memory Use :0M/247M .. .. .. 98 : Memory Use :0M/247M 99 : Memory Use :0M/247M 100 : Memory Use :0M/247M 

Se si esegue qualcosa di critico, di solito rilevo l' Error , lo registro su syserr, quindi lo registro utilizzando il mio framework di registrazione preferito, quindi procedo a rilasciare risorse e chiudere in modo pulito. Qual è la cosa peggiore che può accadere? La JVM sta morendo (o è già morta) comunque e prendendo l' Error c'è almeno una possibilità di pulizia.

L'avvertenza è che devi mirare alla cattura di questi tipi di errori solo nei luoghi in cui è ansible eseguire la pulizia. Non catch(Throwable t) {} coperta catch(Throwable t) {} ovunque o sciocchezze come quella.

Puoi recuperare da esso:

 package com.stackoverflow.q2679330; public class Test { public static void main(String... args) { int size = Integer.MAX_VALUE; int factor = 10; while (true) { try { System.out.println("Trying to allocate " + size + " bytes"); byte[] bytes = new byte[size]; System.out.println("Succeed!"); break; } catch (OutOfMemoryError e) { System.out.println("OOME .. Trying again with 10x less"); size /= factor; } } } } 

Ma ha senso? Cos’altro ti piacerebbe fare? Perché inizialmente alloceresti così tanta memoria? Meno memoria è anche OK? Perché non lo usi già? O se ciò non è ansible, perché non dare alla JVM più memoria dall’inizio?

Torna alle tue domande:

1: ci sono scenari di parole reali quando catturare java.lang.OutOfMemoryError può essere una buona idea?

Nessuno viene in mente.

2: se prendiamo java.lang.OutOfMemoryError come possiamo essere sicuri che il gestore catch non stanzi la memoria da sola (strumenti o migliori pratiche)?

Dipende da cosa ha causato l’OOME. Se è dichiarato al di fuori del blocco try ed è successo passo dopo passo, allora le tue possibilità sono piccole. Si consiglia di riservare un po ‘di spazio in memoria prima:

 private static byte[] reserve = new byte[1024 * 1024]; // Reserves 1MB. 

e quindi impostarlo su zero durante OOME:

 } catch (OutOfMemoryException e) { reserve = new byte[0]; // Ha! 1MB free! } 

Naturalmente tutto ciò non ha assolutamente senso;) Basta fornire a JVM una memoria sufficiente come richiede la tua applicazione. Esegui un profiler, se necessario.

In generale, è una ctriggers idea cercare di catturare e recuperare da una OOM.

  1. Un OOME potrebbe anche essere stato gettato su altri thread, inclusi i thread di cui nemmeno l’applicazione è a conoscenza. Tutti questi fili saranno ora morti e tutto ciò che era in attesa di una notifica potrebbe rimanere bloccato per sempre. In breve, la tua app potrebbe essere interrotta.

  2. Anche se si ripristina correttamente, la JVM potrebbe ancora soffrire di fame di heap e la vostra applicazione si comporterebbe in modo abissale.

La cosa migliore da fare con un OOME è lasciare che la JVM muoia.

(Ciò presuppone che la JVM muoia. Ad esempio, OOM su un thread di servlet Tomcat non uccide la JVM, e questo porta il Tomcat a entrare in uno stato catatonico in cui non risponderà a nessuna richiesta … nemmeno a richieste di ricomincia.)

MODIFICARE

Non sto dicendo che sia una ctriggers idea catturare OOM. I problemi sorgono quando si tenta di recuperare da OOME, deliberatamente o per svista. Ogni volta che catturi una OOM (direttamente o come sottotipo di Errore o Lanciabile), dovresti ricrearla o organizzare che l’applicazione / JVM esca.

A parte questo: per garantire la massima robustezza in presenza di OOM, un’applicazione deve utilizzare Thread.setDefaultUncaughtExceptionHandler () per impostare un gestore che causerà l’uscita dell’applicazione in caso di OOME, indipendentemente dal thread su cui è stato triggersto OOME. Sarei interessato a opinioni su questo …

L’unico altro scenario è quando si è sicuri che l’OOM non ha causato alcun danno collaterale; cioè sai

  • cosa ha causato specificamente l’OOME,
  • cosa stava facendo l’applicazione al momento, e che è OK semplicemente scartare quel calcolo, e
  • che un OOME (approssimativamente) simultaneo non può essersi verificato su un altro thread.

Esistono applicazioni in cui è ansible conoscere queste cose, ma per la maggior parte delle applicazioni non si può essere sicuri che la prosecuzione dopo OOME sia sicura. Anche se empiricamente “funziona” quando lo provi.

(Il problema è che è richiesta una dimostrazione formale per dimostrare che le conseguenze delle OOM “anticipate” sono sicure, e che le “ORE non previste” non possono verificarsi nel controllo di un tentativo / cattura OOME).

Sì, ci sono scenari del mondo reale. Ecco la mia: ho bisogno di elaborare set di dati di molti elementi su un cluster con memoria limitata per nodo. Una determinata istanza di JVM passa attraverso molti elementi uno dopo l’altro, ma alcuni degli elementi sono troppo grandi per essere elaborati sul cluster: posso prendere l’ OutOfMemoryError e prendere nota di quali elementi sono troppo grandi. Più tardi, riesco a rieseguire solo gli elementi di grandi dimensioni su un computer con più RAM.

(Poiché si tratta di una singola allocazione multi-gigabyte di un array che non riesce, la JVM è ancora a posto dopo aver rilevato l’errore e c’è abbastanza memoria per elaborare gli altri elementi.)

Ci sono sicuramente degli scenari in cui catturare un OOME ha senso. IDEA li cattura e apre una finestra di dialogo per consentire di modificare le impostazioni della memoria di avvio (e quindi termina quando hai finito). Un server delle applicazioni potrebbe prenderli e segnalarli. La chiave per farlo è di farlo ad un livello alto nella spedizione in modo da avere una ragionevole possibilità di avere un sacco di risorse liberate nel momento in cui stai rilevando l’eccezione.

Oltre allo scenario IDEA sopra, in generale il catching dovrebbe essere di Throwable, non solo di OOM in particolare, e dovrebbe essere fatto in un contesto in cui almeno il thread sarà terminato a breve.

Naturalmente la maggior parte delle volte la memoria è affamata e la situazione non è recuperabile, ma ci sono modi in cui ha senso.

Mi sono imbattuto in questa domanda perché mi chiedevo se fosse una buona idea catturare OutOfMemoryError nel mio caso. Sto rispondendo qui parzialmente per mostrare ancora un altro esempio quando la cattura di questo errore può avere senso per qualcuno (cioè io) e in parte per scoprire se è una buona idea nel mio caso (con me, essendo uno sviluppatore junior, non posso mai essere troppo sicuro di ogni singola riga di codice che scrivo).

Ad ogni modo, sto lavorando ad un’applicazione Android che può essere eseguita su diversi dispositivi con diverse dimensioni di memoria. La parte pericolosa è la decodifica di una bitmap da un file e la sua visualizzazione in un’istanza di ImageView. Non voglio limitare i dispositivi più potenti in termini di dimensioni della bitmap decodificata, né posso essere sicuro che l’app non verrà eseguita su un dispositivo antico che non ho mai incontrato con memoria molto bassa. Quindi faccio questo:

 BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); bitmapOptions.inSampleSize = 1; boolean imageSet = false; while (!imageSet) { try { image = BitmapFactory.decodeFile(filePath, bitmapOptions); imageView.setImageBitmap(image); imageSet = true; } catch (OutOfMemoryError e) { bitmapOptions.inSampleSize *= 2; } } 

In questo modo riesco a fornire dispositivi sempre più potenti in base alle loro esigenze o aspettative dei loro utenti.

Sì, la vera domanda è “cosa farai nel gestore delle eccezioni?” Per quasi nulla di utile, assegnerai più memoria. Se si desidera eseguire un lavoro di diagnostica quando si verifica un errore OutOfMemoryError, è ansible utilizzare l’ -XX:OnOutOfMemoryError= fornito dalla macchina virtuale HotSpot. Eseguirà i tuoi comandi quando si verifica un errore OutOfMemoryError e puoi fare qualcosa di utile al di fuori dell’heap di Java. In primo luogo, vuoi davvero mantenere l’applicazione a corto di memoria, quindi capire perché succede è il primo passo. Quindi è ansible aumentare la dimensione heap di MaxPermSize come appropriato. Ecco alcuni utili ganci HotSpot:

 -XX:+PrintCommandLineFlags -XX:+PrintConcurrentLocks -XX:+PrintClassHistogram 

Guarda l’elenco completo qui

Ho un’applicazione che deve essere ripristinata dagli errori OutOfMemoryError e nei programmi a thread singolo funziona sempre, ma a volte non funziona nei programmi multi-thread. L’applicazione è uno strumento di test Java automatizzato che esegue sequenze di test generate alla massima profondità ansible sulle classi di test. Ora, l’interfaccia utente deve essere stabile, ma il motore di test può esaurire la memoria mentre cresce l’albero dei casi di test. Lo gestisco con il seguente tipo di idioma di codice nel motore di test:

 boolean isOutOfMemory = false;  // flag utilizzato per la segnalazione
 provare {
    SomeType largeVar;
    // Main loop che assegna sempre più a largeVar
    // può terminare OK, o generare OutOfMemoryError
 }
 catch (OutOfMemoryError ex) {
    // largeVar è ora fuori portata, quindi è spazzatura
    System.gc ();  // ripulire i dati largeVar
    isOutOfMemory = true;  // flag disponibile per l'uso
 }
 // flag dei test del programma per segnalare il recupero

Funziona sempre con le applicazioni a thread singolo. Ma di recente ho inserito il mio motore di test in un thread di lavoro separato dall’interfaccia utente. Ora, la memoria insufficiente può verificarsi arbitrariamente in entrambi i thread e non mi è chiaro come ottenerlo.

Ad esempio, ho avuto l’OOME mentre i fotogrammi di una GIF animata nell’interfaccia utente venivano sottoposti a ciclo da un thread proprietario creato dietro le quinte da una class Swing fuori dal mio controllo. Avevo pensato di aver assegnato tutte le risorse necessarie in anticipo, ma chiaramente l’animatore sta allocando memoria ogni volta che recupera l’immagine successiva. Se qualcuno ha un’idea su come gestire OOMEs sollevato in qualsiasi thread, mi piacerebbe sentire.

L’unica ragione per cui riesco a pensare perché catturare gli errori di OOM potrebbe essere il fatto che tu abbia delle enormi strutture di dati che non usi più, e puoi impostare null e liberare un po ‘di memoria. Ma (1) questo significa che stai sprecando memoria, e dovresti aggiustare il tuo codice piuttosto che zoppicare solo dopo un OOME, e (2) anche se lo prendi, cosa faresti? OOM può accadere in qualsiasi momento, lasciando potenzialmente tutto a metà.

Per la domanda 2 vedo già la soluzione che suggerirei, da BalusC.

  1. C’è qualche vero scenario di parole quando catturare java.lang.OutOfMemoryError può essere una buona idea?

Penso di aver appena trovato un buon esempio. Quando l’applicazione awt invia messaggi, l’errore OutOfMemoryError non visualizzato viene visualizzato su stderr e l’elaborazione del messaggio corrente viene interrotta. Ma l’applicazione continua a funzionare! L’utente può comunque emettere altri comandi inconsapevoli dei gravi problemi che si verificano dietro la scena. Soprattutto quando non può o non osserva l’errore standard. Quindi, l’eccezione di OOM e il riavvio dell’applicazione (o almeno il suggerimento) sono qualcosa che si desidera.

OOME può essere catturato, ma sarà generalmente inutile, a seconda che JVM sia in grado di raccogliere alcuni oggetti quando viene raggiunto il blocco e quanti memoria di heap è rimasta in quel momento.

Esempio: nella mia JVM, questo programma viene eseguito fino alla fine:

 import java.util.LinkedList; import java.util.List; public class OOMErrorTest { public static void main(String[] args) { List ll = new LinkedList(); try { long l = 0; while(true){ ll.add(new Long(l++)); } } catch(OutOfMemoryError oome){ System.out.println("Error catched!!"); } System.out.println("Test finished"); } } 

Tuttavia, l’aggiunta di una sola riga sul fermo ti mostrerà di cosa sto parlando:

 import java.util.LinkedList; import java.util.List; public class OOMErrorTest { public static void main(String[] args) { List ll = new LinkedList(); try { long l = 0; while(true){ ll.add(new Long(l++)); } } catch(OutOfMemoryError oome){ System.out.println("Error catched!!"); System.out.println("size:" +ll.size()); } System.out.println("Test finished"); } } 

Il primo programma funziona bene perché quando viene raggiunto il catch, la JVM rileva che l’elenco non verrà più utilizzato (questo rilevamento può anche essere una ottimizzazione eseguita in fase di compilazione). Quindi, quando raggiungiamo l’istruzione print, la memoria heap è stata liberata quasi interamente, quindi ora abbiamo un ampio margine di manovra per continuare. Questo è il caso migliore.

Tuttavia, se il codice è organizzato come l’elenco ll viene utilizzato dopo che OOME è stato catturato, la JVM non è in grado di raccoglierla. Questo succede nel secondo frammento. L’OOME, triggersto da una nuova creazione Long, viene catturato, ma presto creeremo un nuovo Object (una stringa in System.out,println line) e l’heap è quasi pieno, quindi viene generata una nuova OOME. Questo è lo scenario peggiore: abbiamo provato a creare un nuovo object, abbiamo fallito, abbiamo catturato l’OOME, sì, ma ora la prima istruzione che richiede una nuova memoria heap (ad esempio la creazione di un nuovo object) genererà un nuovo OOME. Pensaci, cos’altro possiamo fare a questo punto con così poca memoria rimasta? Probabilmente semplicemente uscendo. Da qui l’inutile.

Tra le ragioni per cui la JVM non raccoglie risorse, una è davvero spaventosa: una risorsa condivisa con altri thread che ne fanno uso. Chiunque abbia un cervello può vedere quanto può essere pericoloso catturare OOME se inserito in qualche app non sperimentale di qualsiasi tipo.

Sto usando una JVM x86 di Windows x86 (JRE6). La memoria predefinita per ogni app Java è 64 MB.

Ho solo uno scenario in cui catturare un errore OutOfMemory sembra avere senso e sembra funzionare.

Scenario: in un’app per Android, voglio visualizzare più bitmap con la risoluzione più alta ansible e voglio poterli ingrandire in modo fluido.

A causa dello zoom fluente, voglio avere i bitmap in memoria. Tuttavia, Android ha limiti di memoria dipendenti dal dispositivo e difficili da controllare.

In questa situazione, potrebbe esserci OutOfMemoryError durante la lettura della bitmap. Qui, aiuta se lo prendo e poi continuo con una risoluzione più bassa.

Puoi prendere qualsiasi cosa con Throwable, in generale dovresti prendere solo sottoclassi di Exception except RuntimeException (sebbene una grande porzione di sviluppatori catturi anche RuntimeException … ma questo non è mai stato l’intento dei progettisti di linguaggi).

Se dovessi catturare OutOfMemoryError cosa vorresti fare? La VM ha esaurito la memoria, praticamente tutto quello che puoi fare è uscire. Probabilmente non puoi nemmeno aprire una finestra di dialogo per dire che hai memoria insufficiente dato che ciò richiederebbe la memoria 🙂

La VM lancia un OutOfMemoryError quando è veramente senza memoria (infatti tutti gli errori dovrebbero indicare situazioni irrecuperabili) e non dovrebbe esserci nulla che tu possa fare per affrontarlo.

Le cose da fare sono scoprire perché stai esaurendo la memoria (usa un profiler, come quello in NetBeans) e assicurati di non avere perdite di memoria. Se non si verificano perdite di memoria, aumentare la memoria allocata alla VM.

  1. Dipende da come si definisce “buono”. Lo facciamo nella nostra applicazione web buggy e funziona la maggior parte del tempo (per fortuna, ora OutOfMemory non si verifica a causa di una correzione non correlata). Tuttavia, anche se lo si cattura, potrebbe comunque aver violato un codice importante: se si dispone di più thread, l’allocazione della memoria può non riuscire in nessuno di essi. Quindi, a seconda dell’applicazione, c’è ancora il 10–90% di possibilità che si rompa irreversibilmente.
  2. Per quanto ho capito, lo stack pesante che si svolge sulla strada invalida così tanti riferimenti e quindi libera così tanta memoria che non dovresti preoccupartene.

EDIT: ti suggerisco di provarlo. Di ‘, scrivi un programma che chiama ricorsivamente una funzione che assegna progressivamente più memoria. Catch OutOfMemoryError e vedere se è ansible continuare significativamente da quel punto. Secondo la mia esperienza, sarete in grado di, anche se nel mio caso è successo sotto server WebLogic, quindi potrebbe esserci stata qualche magia nera coinvolta.