“Out Of Memory” è un errore ripristinabile?

Ho programmato a lungo, e i programmi che vedo, quando esauriscono la memoria, tentano di pulire e uscire, cioè falliscono con grazia. Non riesco a ricordare l’ultima volta che ne ho visto uno in realtà tentare di recuperare e continuare a funzionare normalmente.

Tanta elaborazione si basa sulla capacità di allocare correttamente la memoria, specialmente nei linguaggi raccolti con garbage collection, sembra che gli errori di memoria esauriti debbano essere classificati come non recuperabili. (Gli errori non recuperabili includono cose come lo stack overflow).

Qual è l’argomento convincente per renderlo un errore recuperabile?

Dipende davvero da cosa stai costruendo.

Non è del tutto irragionevole che un server web fallisca una coppia richiesta / risposta, ma continua ad andare avanti per ulteriori richieste. Dovresti essere sicuro che il singolo fallimento non ha avuto effetti dannosi sullo stato globale, tuttavia – sarebbe stato un po ‘complicato. Dato che un errore causa un’eccezione nella maggior parte degli ambienti gestiti (ad es. .NET e Java), sospetto che se l’eccezione è gestita in “codice utente” sarebbe recuperabile per richieste future – ad esempio se una richiesta tentasse di allocare 10 GB di memoria e fallito, ciò non dovrebbe danneggiare il resto del sistema. Se il sistema esaurisce la memoria durante il tentativo di trasmettere la richiesta al codice utente, tuttavia, questo tipo di cose potrebbe essere più difficile.

In una libreria, si desidera copiare in modo efficiente un file. Quando lo fai, di solito trovi che copiare usando un piccolo numero di pezzi grandi è molto più efficace della copia di molti più piccoli (ad esempio, è più veloce copiare un file da 15 MB copiando 15 pezzi da 1 MB rispetto alla copia di 15.000 Pezzi 1K).

Ma il codice funziona con qualsiasi dimensione del blocco. Quindi, anche se potrebbe essere più veloce con blocchi da 1 MB, se si progetta per un sistema in cui vengono copiati molti file, potrebbe essere saggio catturare OutOfMemoryError e ridurre la dimensione del blocco finché non si riesce.

Un altro posto è una cache per object memorizzato in un database. Si desidera mantenere quanti più oggetti nella cache ansible ma non si vuole interferire con il resto dell’applicazione. Poiché questi oggetti possono essere ricreati, è un modo intelligente di risparmiare memoria per colbind la cache a un gestore di memoria esaurito per eliminare le voci finché il resto dell’app non avrà abbastanza spazio per respirare, ancora una volta.

Infine, per la manipolazione delle immagini, si desidera caricare la maggior parte ansible dell’immagine nella memoria. Ancora una volta, un gestore OOM consente di implementarlo senza conoscere in anticipo la quantità di memoria che l’utente o il sistema operativo concederanno al codice.

[EDIT] Nota che io lavoro partendo dal presupposto che hai dato all’applicazione una quantità fissa di memoria e questa quantità è inferiore alla memoria disponibile totale escludendo lo spazio di swap. Se è ansible allocare così tanta memoria che parte di essa deve essere scambiata, molti dei miei commenti non hanno più senso.

Gli utenti di MATLAB esauriscono la memoria tutto il tempo quando eseguono operazioni aritmetiche con array di grandi dimensioni. Ad esempio se la variabile x si adatta alla memoria e lancia “x + 1”, MATLAB assegna lo spazio per il risultato e lo riempie. Se l’allocazione fallisce gli errori MATLAB e l’utente può provare qualcos’altro. Sarebbe un disastro se MATLAB uscisse ogni volta che si presentava questo caso d’uso.

OOM dovrebbe essere recuperabile perché l’arresto non è l’unica strategia per il ripristino da OOM.

In realtà esiste una soluzione piuttosto standard per il problema di OOM a livello di applicazione. Come parte del progetto dell’applicazione, è necessario determinare una quantità minima di memoria necessaria per il ripristino da una condizione di memoria insufficiente. (Ad esempio, la memoria necessaria per salvare automaticamente i documenti, far apparire le windows di avvertimento, registrare i dati di arresto).

All’inizio della tua applicazione o all’inizio di un blocco critico, pre-allocare quella quantità di memoria. Se si rileva una condizione di memoria insufficiente, rilasciare la memoria di protezione ed eseguire il ripristino. La strategia può ancora fallire, ma nel complesso dà un grande successo.

Si noti che l’applicazione non deve essere chiusa. Può visualizzare una finestra di dialogo modale fino a quando la condizione di OOM non è stata risolta.

Non sono sicuro al 100%, ma sono abbastanza sicuro che ” Code Complete ” (la lettura richiesta per ogni rispettabile software engineer) lo copra.

PS È ansible estendere il framework dell’applicazione per aiutare con questa strategia, ma per favore non implementare tale politica in una libreria (le buone librerie non prendono decisioni globali senza il consenso di un’applicazione)

Penso che come molte cose, è un’analisi costi / benefici. È ansible programmare in un tentativo di recupero da un errore di malloc (), anche se può essere difficile (il gestore potrebbe non cadere nella stessa carenza di memoria che deve gestire).

Hai già notato che il caso più comune è ripulire e fallire con grazia. In tal caso è stato deciso che il costo di abortire con garbo è inferiore alla combinazione dei costi di sviluppo e del costo delle prestazioni nel recupero.

Sono sicuro che puoi pensare ai tuoi esempi di situazioni in cui terminare il programma è un’opzione molto costosa (macchina di supporto vitale, controllo della nave spaziale, calcolo finanziario a lungo termine e critico, ecc.) – sebbene la prima linea di difesa sia naturalmente per garantire che il programma abbia un utilizzo prevedibile della memoria e che l’ambiente possa fornirlo.

Sto lavorando su un sistema che alloca la memoria per la cache di I / O per aumentare le prestazioni. Quindi, al rilevamento di OOM, ne richiede un po ‘indietro, in modo che la logica aziendale possa procedere, anche se ciò significa meno cache di I / O e prestazioni di scrittura leggermente inferiori.

Ho anche lavorato con applicazioni Java integrate che tentavano di gestire OOM forzando la garbage collection, eventualmente rilasciando alcuni oggetti non critici, come dati precaricati o memorizzati nella cache.

I principali problemi con la gestione di OOM sono:

1) essere in grado di riprovare nel luogo in cui è successo o essere in grado di tornare indietro e riprovare da un punto più alto. La maggior parte dei programmi contemporanei si basano troppo sulla lingua da utilizzare e non riescono a gestire realmente dove finiscono e come riprovare l’operazione. Di solito il contesto dell’operazione andrà perso, se non è stato progettato per essere conservato

2) essere in grado di rilasciare effettivamente della memoria. Questo significa un tipo di gestore risorse che sa quali oggetti sono critici e quali no, e il sistema può richiedere nuovamente gli oggetti rilasciati quando e se in seguito diventano critici

Un altro problema importante è essere in grado di eseguire il rollback senza triggersre un’altra situazione OOM. Questo è qualcosa che è difficile da controllare nei linguaggi di alto livello.

Inoltre, il SO sottostante deve comportarsi in modo prevedibile rispetto a OOM. Linux, per esempio, non lo farà, se l’overcommit della memoria è abilitato. Molti sistemi abilitati per lo scambio moriranno prima di segnalare l’OOM all’applicazione in questione.

E, c’è il caso in cui non è il tuo processo che ha creato la situazione, quindi liberare memoria non aiuta se il processo incriminato continua a perdere.

A causa di tutto ciò, spesso sono i sistemi grandi e incorporati che impiegano queste tecniche, poiché hanno il controllo sul sistema operativo e sulla memoria per consentire loro, e la disciplina / motivazione per implementarle.

È recuperabile solo se lo prendi e lo gestisci correttamente.

Negli stessi casi, ad esempio, una richiesta ha tentato di allocare un sacco di memoria. È abbastanza prevedibile e puoi gestirlo molto bene.

Tuttavia, in molti casi nell’applicazione multi-thread, OOE può anche accadere sul thread in background (incluso creato dal sistema / libreria di terze parti). È quasi imansible da prevedere e potresti non riuscire a recuperare lo stato di tutti i tuoi thread.

No. Un errore di memoria insufficiente dal GC non dovrebbe generalmente essere recuperabile all’interno del thread corrente. (Tuttavia, la creazione e la terminazione del thread recuperabile (utente o kernel) dovrebbero essere supportate)

Per quanto riguarda i contro esempi: sto attualmente lavorando a un progetto di linguaggio di programmazione D che utilizza la piattaforma CUDA di NVIDIA per il calcolo GPU. Invece di gestire manualmente la memoria della GPU, ho creato oggetti proxy per sfruttare il GC di D’s. Pertanto, quando la GPU restituisce un errore di memoria esaurita, eseguo una raccolta completa e sollevo un’eccezione solo se fallisce una seconda volta. Ma questo non è davvero un esempio di recupero di memoria esaurito, è più uno di integrazione GC. Gli altri esempi di recupero (cache, liste libere, stack / hash senza auto-shrinking, ecc.) Sono tutte strutture che hanno i propri metodi di raccolta / compattazione della memoria che sono separati dal GC e tendono a non essere locali all’assegnazione funzione. Quindi le persone potrebbero implementare qualcosa di simile al seguente:

 T new2(T)( lazy T old_new ) { T obj; try{ obj = old_new; }catch(OutOfMemoryException oome) { foreach(compact; Global_List_Of_Delegates_From_Compatible_Objects) compact(); obj = old_new; } return obj; } 

Quale è un argomento decente per aggiungere il supporto per la registrazione / annullamento della registrazione degli oggetti di raccolta / compattazione in generale per i garbage collector.

Nel caso generale, non è recuperabile.

Tuttavia, se il sistema include qualche forma di memorizzazione nella cache dynamic, un gestore esaurito può spesso scaricare gli elementi più vecchi nella cache (o anche l’intera cache).

Ovviamente, devi assicurarti che il processo di “dumping” non richieda nuove allocazioni di memoria 🙂 Inoltre, può essere complicato recuperare l’allocazione specifica che ha avuto esito negativo, a meno che tu non sia in grado di colbind il codice di dumping della cache direttamente all’allocatore. livello, in modo che l’errore non venga propagato al chiamante.

Dipende da cosa intendi esaurendo la memoria.

Quando malloc() fallisce nella maggior parte dei sistemi, è perché hai esaurito lo spazio degli indirizzi.

Se la maggior parte di quella memoria viene presa dalla cache, o dalle regioni di mmap’d, potresti essere in grado di recuperarne una parte liberando la cache o annullando la cancellazione. Tuttavia, questo richiede davvero che tu sappia per cosa stai usando quella memoria e, come hai notato, la maggior parte dei programmi non lo fa, o non fa differenza.

Se hai usato setrlimit() su te stesso (per proteggerti da attacchi unforseen, forse, o forse da root hai fatto a te), puoi rilassare il limite nel tuo gestore di errori. Lo faccio molto spesso, dopo aver richiesto all’utente se ansible, e aver registrato l’evento.

D’altra parte, l’overflow dello stack di cattura è un po ‘più difficile e non è portabile. Ho scritto una soluzione posixish per ECL e ho descritto un’implementazione di Windows, se stai seguendo questa strada. È stato archiviato in ECL qualche mese fa, ma se riesci ti posso recuperare le patch originali.

La domanda è contrassegnata come “linguaggio indipendente”, ma è difficile rispondere senza considerare la lingua e / o il sistema sottostante. (Vedo parecchi hadns

Se l’allocazione della memoria è implicita, senza alcun meccanismo per rilevare se una determinata allocazione è riuscita o meno, allora il recupero da una condizione di esaurimento della memoria può essere difficile o imansible.

Ad esempio, se si chiama una funzione che tenta di allocare un array enorme, la maggior parte delle lingue non definisce il comportamento se non è ansible allocare l’array. (In Ada questo solleva un’eccezione Storage_Error , almeno in linea di principio, e dovrebbe essere ansible gestirla.)

D’altra parte, se si dispone di un meccanismo che tenta di allocare memoria ed è in grado di segnalare un errore (come C malloc() o C ++ di new ), allora sì, è certamente ansible recuperare da tale errore. Almeno nei casi di malloc() e new , un’assegnazione non riuscita non fa altro che segnalare un errore (ad esempio, non danneggia le strutture di dati interne).

Se abbia senso provare a recuperare dipende dall’applicazione. Se l’applicazione non può avere successo solo dopo un errore di allocazione, allora dovrebbe fare qualsiasi operazione di pulizia e terminazione. Ma se l’errore di allocazione significa semplicemente che non è ansible eseguire un’attività specifica o se l’attività può ancora essere eseguita più lentamente con meno memoria, allora ha senso continuare a funzionare.

Un esempio concreto: Supponiamo che io stia usando un editor di testo. Se provo a eseguire qualche operazione all’interno dell’editor che richiede molta memoria, e che l’operazione non può essere eseguita, voglio che l’editor mi dica che non può fare ciò che ho chiesto e lasciarmi continuare a modificare . Terminare senza salvare il mio lavoro sarebbe una risposta inaccettabile. Salvare il mio lavoro e terminare sarebbe meglio, ma è ancora inutilmente ostile all’utente.

Questa è una domanda difficile. A prima vista sembra che non avere più memoria significhi “sfortuna” ma, bisogna anche vedere che si può sbarazzarsi di molte cose legate alla memoria se si insiste davvero. Prendiamo semplicemente in altri modi la funzione rotta strtok che da una parte non ha problemi con la memoria. Quindi prendi come contropartita g_string_split dalla libreria Glib, che dipende in gran parte dall’allocazione della memoria come quasi tutto nei programmi basati su glib o su GObject. Si può sicuramente affermare che nei linguaggi più dinamici l’allocazione della memoria è molto più utilizzata come nelle lingue più rigide, specialmente C. Ma vediamo le alternative. Se si termina il programma solo se si esaurisce la memoria, anche il codice attentamente sviluppato potrebbe smettere di funzionare. Ma se hai un errore recuperabile, puoi fare qualcosa al riguardo. Quindi, l’argomento, rendendolo recuperabile, significa che si può scegliere di “gestire” quella situazione in modo diverso (ad esempio mettendo da parte un blocco di memoria per le emergenze o la degradazione a un programma esteso di memoria minore).

Quindi la ragione più convincente è. Se si fornisce un modo di recuperare si può provare il recupero, se non si ha la scelta tutto dipende sempre da avere abbastanza memoria …

Saluti

Mi sta solo sconcertando ora.

Al lavoro, abbiamo un insieme di applicazioni che lavorano insieme e la memoria sta per esaurirsi. Mentre il problema è far sì che il pacchetto di applicazioni diventi 64-bit (e quindi, essere in grado di lavorare oltre i limiti di 2 Go che abbiamo su un normale sistema operativo Win32), e / o ridurre il nostro uso della memoria, questo problema di “Come recuperare da una OOM “non mi abbandonerà la testa.

Naturalmente, non ho una soluzione, ma gioco ancora alla ricerca di uno per C ++ (principalmente per RAII ed eccezioni, principalmente).

Forse un processo che si suppone di recuperare con grazia dovrebbe suddividere il suo trattamento in compiti atomici / rollbackabili (cioè usando solo funzioni / metodi che offrono una garanzia di eccezione forte / nothrow), con un “buffer / pool di memoria” riservato ai fini del recupero.

Se uno dei task fallisce, il C ++ bad_alloc dovrebbe scaricare lo stack, liberare memoria stack / heap attraverso RAII. La funzione di recupero recupererebbe il più ansible (salvando i dati iniziali dell’attività sul disco, da utilizzare in un tentativo successivo) e forse registrerebbe i dati dell’attività per un tentativo successivo.

Credo che l’uso di C ++ strong / nothrow guanrantees possa aiutare un processo a sopravvivere in condizioni di scarsa memoria disponibile, anche se sarebbe uno scambio di memoria (cioè lento, in qualche modo irrisolto, ecc.), Ma ovviamente, questo è solo teoria. Ho solo bisogno di diventare più intelligente sull’argomento prima di provare a simularlo (ad esempio, creando un programma C ++, con un allocatore personalizzato di nuova / eliminazione con memoria limitata, e quindi provare a fare un po ‘di lavoro in quelle condizioni stressanti).

Bene…

Soprattutto negli ambienti di raccolta dei rifiuti, è probabile che, se si riscontra l’errore OutOfMemory ad un livello elevato dell’applicazione, molte cose sono andate oltre lo scopo e possono essere recuperate per darti la memoria posteriore.

In caso di allocazioni eccessive singole, l’app può essere in grado di continuare a funzionare in modo impeccabile. Naturalmente, se si dispone di una perdita di memoria graduale, si verificherà di nuovo il problema (molto probabilmente prima o poi), ma è comunque una buona idea dare all’app la possibilità di andare giù con grazia, salvare le modifiche non salvate nel caso di un’app GUI, ecc.

Sì, OOM è recuperabile. Come esempio estremo, i sistemi operativi Unix e Windows si ripristinano abbastanza bene dalle condizioni OOM, il più delle volte. Le applicazioni falliscono, ma il sistema operativo sopravvive (supponendo che ci sia abbastanza memoria per il sistema operativo da avviare correttamente in primo luogo).

Cito solo questo esempio per dimostrare che si può fare.

Il problema di gestire OOM dipende in realtà dal tuo programma e dal tuo ambiente.

Ad esempio, in molti casi il luogo in cui si verifica l’OOM molto probabilmente NON è il posto migliore per il ripristino effettivo da uno stato di OOM.

Ora, un allocatore personalizzato potrebbe funzionare come punto centrale all’interno del codice che può gestire una OOM. L’allocatore Java eseguirà un GC completo prima di generare un’eccezione OOM.

Quanto più “application aware” è il tuo allocatore, tanto più adatto sarebbe come gestore centrale e agente di recupero per OOM. Usando di nuovo Java, l’allocatore non è particolarmente attento all’applicazione.

Questo è dove qualcosa come Java è prontamente frustrante. Non è ansible sovrascrivere l’allocatore. Quindi, mentre si possono intercettare le eccezioni OOM nel proprio codice, non c’è nulla che dica che alcune librerie che si stanno utilizzando siano in grado di intrappolare correttamente, o anche correttamente, di LANCIARE un’eccezione di OOM. È banale creare una class rovinata per sempre da un’eccezione di OOM, dato che alcuni oggetti vengono impostati su null e “che non succede mai” e non è mai recuperabile.

Quindi, sì, OOM è recuperabile, ma può essere MOLTO difficile, in particolare in ambienti moderni come Java ed è pieno di librerie di terze parti di varia qualità.

Memoria esaurita normalmente significa che devi lasciare tutto ciò che stavi facendo. Se si presta attenzione alla pulizia, tuttavia, può lasciare il programma stesso operativo e in grado di rispondere ad altre richieste. È meglio che un programma dica “Scusa, non abbastanza memoria da fare” che “Scusa, memoria esaurita, spegnimento”.

La memoria insufficiente può essere causata dall’esaurimento della memoria libera o dal tentativo di allocare un blocco irragionevolmente grande (come un concerto). Nei casi di “esaurimento”, la carenza di memoria è globale per il sistema e di solito influisce su altre applicazioni e servizi di sistema e l’intero sistema potrebbe diventare instabile, quindi è consigliabile dimenticarsi e riavviare. Nei casi di “irragionevole grande blocco” non si verifica alcuna carenza ed è sicuro continuare. Il problema è che non puoi rilevare automaticamente il caso in cui ti trovi. Quindi è più sicuro rendere l’errore non recuperabile e trovare una soluzione alternativa per ogni caso in cui si verifica questo errore: fai in modo che il tuo programma utilizzi meno memoria o, in alcuni casi, risolva solo bug nel codice che richiama l’allocazione della memoria.

Ci sono già molte buone risposte qui. Ma mi piacerebbe contribuire con un’altra prospettiva.

L’esaurimento di quasi tutte le risorse riutilizzabili dovrebbe essere recuperabile in generale. Il ragionamento è che ciascuna parte di un programma è fondamentalmente un sottoprogramma. Solo perché un sottotitolo non può completare alla fine in questo preciso momento, non significa che l’intero stato del programma sia spazzatura. Solo perché il parcheggio è pieno di macchine non significa che tu rifiuti la tua auto. O aspetti un po ‘che una cabina sia libera, o vai in un negozio più lontano per comprare i tuoi biscotti.

Nella maggior parte dei casi c’è un modo alternativo. Fare un errore irrecuperabile, rimuove in modo efficace molte opzioni, e nessuno di noi ama avere qualcuno che decida per noi ciò che possiamo e non possiamo fare.

Lo stesso vale per lo spazio su disco. È davvero lo stesso ragionamento. E contrariamente alla tua insinuazione riguardo allo stack overflow non è recuperabile, direi che è una limitazione arbitraria. Non c’è una buona ragione per cui non si dovrebbe essere in grado di lanciare un’eccezione (facendo scattare molti fotogrammi) e quindi usare un altro approccio meno efficiente per portare a termine il lavoro.

I miei due centesimi 🙂

Se sei veramente senza memoria sei condannato, dato che non puoi liberare più nulla.

Se hai esaurito la memoria, ma qualcosa come un garbage collector può entrare e liberare un po ‘di memoria non sei ancora morto.

L’altro problema è la frammentazione. Anche se potresti non essere a corto di memoria (frammentato), potresti non essere ancora in grado di allocare l’enorme porzione che desideri.

So che hai chiesto argomenti, ma posso solo vedere argomenti contro.

Non vedo comunque di ottenere ciò in un’applicazione multi-thread. Come fai a sapere quale thread è effettivamente responsabile per l’errore di memoria insufficiente? Un thread potrebbe allocare costantemente nuova memoria e avere gc-root al 99% dell’heap, ma la prima allocazione che ha esito negativo si verifica in un altro thread.

Un esempio pratico: ogni volta che ho verificato un OutOfMemoryError nella nostra applicazione Java (in esecuzione su un server JBoss), non è come un thread muore e il resto del server continua a girare: no, ci sono diverse OOME, uccidendo diversi thread (alcuni di cui sono i thread interni di JBoss). Non vedo cosa io possa fare come programmatore per riprendermi da quello – o anche cosa potrebbe fare JBoss per recuperare da esso. In effetti, non sono nemmeno sicuro che tu possa: javadoc per VirtualMachineError suggerisce che la JVM potrebbe essere “rotta” dopo che è stato generato un errore di questo tipo. Ma forse la domanda era più mirata alla progettazione della lingua.

uClibc ha un buffer statico interno di 8 byte o così per I / O di file quando non c’è più memoria da allocare dynamicmente.

Qual è l’argomento convincente per renderlo un errore recuperabile?

In Java, un argomento convincente per non renderlo un errore recuperabile è perché Java consente a OOM di essere segnalato in qualsiasi momento, anche nei momentjs in cui il risultato potrebbe essere il tuo programma che entra in uno stato incoerente. Il ricupero affidabile da una OOM è quindi imansible; se rilevi l’eccezione di OOM, non puoi fare affidamento su nessuno dei tuoi stati del programma. Vedi Garanzie di VirtualMachineError No-throw

Ho questo:

 void *smalloc(size_t size) { void *mem = null; for(;;) { mem = malloc(size); if(mem == NULL) { sleep(1); } else break; } return mem; } 

Quale ha salvato un sistema già alcune volte. Solo perché hai esaurito la memoria ora, non significa che qualche altra parte del sistema o altri processi in esecuzione sul sistema abbiano una memoria che restituiranno presto. Sarà meglio essere molto molto attenti prima di tentare questi trucchi e avere il controllo su ogni memoria che assegnerai al tuo programma.