OutOfMemoryError è un'eccezione fatale che si verifica quando la Macchina Virtuale Java (JVM) o Android Runtime (ART) non può allocare memoria per un nuovo oggetto a causa della mancanza di spazio nell'Heap. Secondo Square Engineering, il 70% degli OutOfMemoryError nelle applicazioni mobili è causato da perdite di memoria, non dal superamento effettivo del limite. Comprendere le cause di OOM è la chiave per la stabilità dell'applicazione.
Punti Chiave
OutOfMemoryError (OOM) è un'eccezione della famiglia VirtualMachineError in Java/Kotlin che segnala l'incapacità di allocare memoria per un nuovo oggetto. A differenza delle eccezioni verificate, OOM è un Error e non richiede gestione tramite catch — sebbene tecnicamente possa essere catturato. Dopo che si verifica un OOM, l'applicazione è solitamente in uno stato instabile e si consiglia di terminarla.
Su Android, ogni applicazione ha un limite di Heap impostato dal produttore del dispositivo. Per gli smartphone moderni con 6+ GB di RAM, il limite è di 256–512 MB, per i dispositivi economici — 128–192 MB. Quando il volume totale di tutti gli oggetti vivi supera questo limite, ART lancia OutOfMemoryError.
È importante capire: OOM non significa sempre che il dispositivo abbia esaurito la memoria fisica. Significa che l'applicazione ha esaurito il suo limite di Heap impostato dal sistema. Altre applicazioni possono avere memoria libera, ma la tua applicazione non può usarla a causa dell'isolamento dei processi in Android.
Cinque scenari portano regolarmente a OOM nelle applicazioni mobili. Ogni scenario è associato a un tipo specifico di dati o operazione.
Bitmap è il principale consumatore di memoria nelle applicazioni Android. Caricare un'immagine FullHD (1920 × 1080) a dimensione originale occupa 8,3 MB in formato ARGB_8888. Se ci sono 50 di queste immagini in un RecyclerView, sono 415 MB, superando l'Heap di qualsiasi dispositivo. Caricare immagini senza inSampleSize garantisce OOM sui dispositivi deboli.
Usa Glide o Coil per il ridimensionamento automatico. Queste librerie caricano le immagini con una dimensione corrispondente alla View, non alla risoluzione originale. Per l'uso diretto di BitmapFactory.Options, applica inSampleSize: calcolalo come potenza di due in modo che la dimensione finale non superi 2048 × 2048 pixel. Inoltre, usa RGB_565 invece di ARGB_8888 per le immagini senza trasparenza — questo dimezza il consumo di memoria.
fun loadScaledBitmap(path: String, reqWidth: Int): Bitmap? {
val opts = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeFile(path, opts)
opts.inSampleSize = calculateSampleSize(opts.outWidth, reqWidth)
opts.inJustDecodeBounds = false
return BitmapFactory.decodeFile(path, opts)
}
Una singola perdita di pochi KB non causerà OOM. Ma decine di perdite su ogni schermo si accumulano: ogni transizione di schermo aggiunge una perdita, il GC non può liberare oggetti e l'Heap si riempie. Un pattern tipico: l'utente apre e chiude la schermata del profilo 20 volte → l'Heap cresce di 200 MB → l'applicazione si blocca con OOM.
Installa LeakCanary nel progetto per il rilevamento automatico delle perdite. Mostrerà ogni oggetto perso con uno stack trace esatto. Dopo aver corretto tutte le perdite, il consumo di Heap diventa stabile: dopo aver chiuso uno schermo, la memoria torna al livello base.
Caricare interi file in byte[] è una strada diretta verso OOM. Un file JSON di 50 MB durante l'analisi creerà una stringa della stessa dimensione più un modello DOM. File video caricati in memoria, buffer audio e grandi set di dati protobuf — tutti possono superare il limite di Heap in una singola operazione.
Elabora i dati grandi usando flussi: InputStream con buffer di 4–8 KB, parser JSON in streaming (Jackson o Gson con JsonReader), MediaCodec per video. Non chiamare mai File.readBytes() su file più grandi del 10% dell'Heap disponibile.
La creazione intensiva di oggetti in un ciclo senza GC intermedio può portare a OOM, specialmente su dispositivi con Heap piccolo. Esempio: generare 100.000 oggetti in un for-loop che non entrano nell'Heap prima che il GC possa raccoglierli. Questo è più comune nei giochi e negli editor grafici.
Usa Object Pool per oggetti che vengono creati e distrutti in massa. Per dati numerici, usa primitivi (FloatArray invece di List<Float>). RecyclerView con ViewHolder Pool risolve questo problema per i componenti dell'interfaccia utente.
La frammentazione è uno stato in cui c'è abbastanza memoria libera in totale, ma nessun blocco contiguo per un nuovo oggetto. ART compatta l'Heap durante il GC, ma non sempre con successo. I grandi array (Bitmap, byte[]) sono i più sensibili alla frammentazione.
ART su Android 8+ usa GC Generazionale, che riduce la frammentazione separando oggetti giovani e vecchi. Tuttavia, evita di allocare frammenti di dimensioni diverse nello stesso pool — cerca di usare buffer preallocati di dimensione fissa.
Il limite di Heap in Android non è una costante — dipende dal produttore, dal modello del dispositivo e dalla versione del sistema operativo. Google stabilisce requisiti minimi attraverso il Documento di Definizione di Compatibilità (CDD), ma i produttori impostano i valori effettivi.
| Categoria Dispositivo | Heap Tipico | largeHeap |
|---|---|---|
| Economico (1–2 GB RAM) | 128–192 MB | 256–384 MB |
| Media gamma (3–4 GB RAM) | 256–384 MB | 512 MB |
| Flagship (6+ GB RAM) | 384–512 MB | 768 MB–1 GB |
| Tablet (4+ GB RAM) | 256–512 MB | 768 MB |
| Wear OS | 32–64 MB | N/D |
Puoi richiedere un limite aumentato tramite android:largeHeap="true" nel manifesto. Usalo con cautela: aumentare l'Heap non risolve il problema delle perdite e può peggiorare l'esperienza utente se il sistema è costretto a uccidere altre applicazioni per liberare memoria per la tua. Per Wear OS, il limite di Heap è minimo — solo 32–64 MB, largeHeap non è disponibile qui, e il risparmio di memoria è doppiamente critico.
La diagnosi di OOM richiede l'analisi di un Heap Dump e la comprensione di quali oggetti consumano memoria. Android Studio fornisce tutti gli strumenti necessari.
Passo 1: Cattura il momento dell'OOM. In Android Memory Profiler, fai clic su Record memory allocations ed esegui lo scenario che causa il crash. Il Profiler mostrerà un picco nelle allocazioni prima dell'OOM. Se OOM non è riproducibile, riduci l'Heap tramite android:smallHeap nella build di debug o usa DDMS con chiamata manuale di GC.
Passo 2: Prendi un Heap Dump al carico di picco (prima dell'OOM). Apri il Dump in Android Studio: la scheda Classes è ordinata per Retained Size. Gli oggetti più grandi sono Bitmap, byte[], String. Per ogni Bitmap, controlla la dimensione (larghezza × altezza × 4 byte) e il percorso di caricamento tramite Stack Trace.
Passo 3: Analizza il numero di oggetti duplicati. Se vedi 200 Fragment o Activity identici — è una perdita. Se 500 Bitmap con la stessa dimensione — è un problema di caching delle immagini. MAT (Memory Analyzer Tool) fornisce un'analisi più approfondita con un Dominator Tree che mostra quali oggetti trattengono l'80% dell'Heap.
// Comando Heap Dump tramite adb
adb shell am dumpheap com.example.app /data/local/tmp/dump.hprof
adb pull /data/local/tmp/dump.hprof.
Una strategia completa di prevenzione di OOM include cinque livelli di protezione: dalle decisioni architetturali al monitoraggio in produzione.
ViewModel + Repository separa i dati dall'interfaccia utente e impedisce il trattenimento della View durante la rotazione dello schermo. ViewModel sopravvive all'Activity, i suoi dati non vengono persi e la View può essere ricreata senza duplicare i dati in memoria. Usa StateFlow invece di LiveData per la gestione esplicita degli stati.
Glide è una libreria obbligatoria per lavorare con le immagini. Ridimensiona, memorizza nella cache (disco + memoria) e ricicla automaticamente i Bitmap. Configura diskCacheStrategy e skipMemoryCache per elenchi grandi. Per immagini animate, usa Glide con GIF/WebP — occupano meno memoria di una sequenza di Bitmap.
Firebase Performance Monitoring traccia il consumo di memoria in tempo reale. Imposta un avviso quando l'uso dell'Heap supera l'80% del limite — è un segnale per indagare. Crashlytics raccoglie OOM come eccezione e mostra l'ultimo stato noto dell'Heap prima del crash. Per Android 11+, usa ApplicationExitInfo per rilevare terminazioni OOM.
Assicurati di testare l'applicazione su dispositivi con Heap minimo (128–192 MB). Un emulatore con schermo piccolo e Heap piccolo emula un dispositivo economico. Se l'applicazione funziona su tale dispositivo, non ci saranno problemi di OOM sui flagship. Usa Firebase Test Lab con dispositivi reali di diverse fasce di prezzo.
// Verifica Heap disponibile prima di operazione pesante
fun canAllocate(requiredBytes: Long): Boolean {
val runtime = Runtime.getRuntime()
val free = runtime.freeMemory()
return free > requiredBytes * 2 // margine del 50%
}
Domande Frequenti
Tecnicamente sì, ma non è raccomandato. Dopo OOM, l'applicazione è in uno stato instabile: nuove allocazioni potrebbero fallire e alcuni oggetti potrebbero essere parzialmente creati. L'unica azione ragionevole in catch è la registrazione e il riavvio dell'Activity.
Il limite di Heap varia tra i dispositivi. Un'operazione che richiede 300 MB fallirà su un dispositivo con limite di 192 MB ma avrà successo su un flagship con 512 MB. Testa su dispositivi con specifiche minime per rilevare scenari OOM.
largeHeap aumenta il limite ma non accelera l'applicazione. Le pause del GC diventano più lunghe poiché la raccolta di un Heap grande richiede più tempo. Il sistema potrebbe uccidere applicazioni in background per fornire memoria. Usa largeHeap solo per applicazioni che oggettivamente necessitano di molta memoria (fotocamere, editor).
OOM è un'eccezione all'interno di un'applicazione quando l'Heap è insufficiente. Un kill di sistema (Low Memory Killer) è una decisione del kernel Linux di uccidere un processo per liberare memoria per altre applicazioni. In un kill di sistema, l'applicazione non riceve un'eccezione — il processo semplicemente termina.
La formula: larghezza × altezza × bytesPerPixel. ARGB_8888 = 4 B/pixel, RGB_565 = 2 B/pixel. Un Bitmap FullHD (1920 × 1080) in ARGB_8888 = 8,3 MB. Un Bitmap 4K (3840 × 2160) = 33 MB. Ridimensiona sempre le immagini alla dimensione necessaria per la visualizzazione sullo schermo.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche