Fuga di memoria — uno dei problemi più insidiosi nello sviluppo mobile. L'uso della memoria dell'app cresce costantemente fino a raggiungere il limite impostato dal sistema operativo, seguito da un OutOfMemoryError o da una terminazione forzata. Secondo Square Engineering, circa il 40% delle app Android ha almeno una fuga di memoria rilevabile solo tramite profilazione. Esaminiamo le cause e i metodi per prevenire la crescita della memoria.
Punti chiave
Fuga di memoria — una situazione in cui un oggetto non più necessario all'app continua a essere trattenuto nell'heap perché un riferimento attivo dal set radice (GC Root) punta ancora ad esso. Il garbage collector considera tale oggetto vivo e non lo rimuove.
Gonfiore della memoria — un problema più ampio in cui l'app consuma più memoria del necessario per eseguire le attività correnti. Cause: caching eccessivo, duplicazione di oggetti, strutture dati non ottimali e frammentazione dell'heap.
In Android, a ogni app viene allocato un heap limitato (solitamente 64–512 MB a seconda del dispositivo e della versione del sistema operativo). In iOS, il limite è meno rigido, ma il sistema invia un avviso di memoria quando ci si avvicina al limite.
| Caratteristica | Android | iOS |
|---|---|---|
| Limite heap | 64–512 MB (dipende dal dispositivo) | Implicito (sistema) |
| Garbage collection | ART (Concorrente, Compatto) | ARC (Conteggio automatico dei riferimenti) |
| Meccanismo di fuga | Riferimenti GC Root | Retain cycle (cicli di riferimenti forti) |
| Risultato | OutOfMemoryError | Avviso di memoria → terminazione |
Secondo il Facebook Engineering Blog, le fughe di memoria causano ~15% delle segnalazioni di crash nelle app mobili. In Android, si aggiungono gli ANR dovuti a frequenti pause del GC in caso di memoria insufficiente.
Riferimento statico ad Activity — una fuga classica in Android. Se un campo statico o singleton mantiene un riferimento a un'Activity, essa non verrà raccolta dal GC nemmeno dopo finish() finché il singleton è vivo. Un'Activity è un oggetto pesante che contiene una gerarchia di view, risorse e Context.
object LeakHolder {
var activityRef: Activity ?= null // leak: static reference to Activity
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle ?= null) {
super.onCreate(savedInstanceState)
LeakHolder.activityRef = this // ❌ MainActivity will never be GC'd
}
}
Classi anonime e lambda — trattengono implicitamente un riferimento alla classe esterna. Se un Runnable o Callback viene passato a un servizio esterno e l'Activity viene distrutta, l'oggetto della classe anonima resta in coda e impedisce all'Activity di essere raccolta.
In iOS, il problema principale sono i retain cycle: due oggetti mantengono riferimenti forti l'uno verso l'altro e l'ARC non può azzerare il contatore dei riferimenti per nessuno dei due. Un caso tipico: una closure che cattura self fortemente e self che mantiene un riferimento alla closure.
LeakCanary — una libreria di Square per il rilevamento automatico delle fughe in Android. Dopo la distruzione di un'Activity o di un Fragment, verifica se l'oggetto è stato raccolto dal GC. In caso contrario, esegue un heap dump e mostra la traccia della fuga.
// LeakCanary 2.x — auto-integration via Application
class ExampleApplication : Application() {
override fun onCreate() {
super.onCreate()
// LeakCanary auto-installs in debug build
// via ContentProvider — zero code setup
}
}
// Force check invocation
AppWatcher.objectWatcher.watch(watchedObject, "leak description")
Android Studio Profiler — uno strumento integrato per il monitoraggio della memoria in tempo reale. Consente di registrare un heap dump, trovare oggetti sospetti (Retained Size > 1 MB) e tracciare il percorso GC root fino a ciascun oggetto.
Per iOS, utilizzate il Xcode Memory Graph Debugger. Visualizza il grafo degli oggetti in memoria, mostra i retain cycle e consente di rilevare immediatamente i riferimenti circolari. Instruments > Allocations è disponibile anche per il monitoraggio a lungo termine.
WeakReference — un meccanismo di base per i riferimenti che non devono interferire con la garbage collection. Se il GC decide di raccogliere un oggetto, WeakReference restituisce null. Viene utilizzato per callback, listener e riferimenti a componenti UI da thread in background.
Componenti Lifecycle-aware — un approccio architetturale implementato in Android Jetpack (Lifecycle, LiveData, Flow, coroutines). Le sottoscrizioni vengono automaticamente cancellate a onDestroy, eliminando la principale classe di fughe.
class MyViewModel : ViewModel() {
private val _data = MutableLiveData<List<User>>()
val data: LiveData<List<User>> get() = _data
fun loadData() {
viewModelScope.launch {
val result = repository.fetchData()
_data.postValue(result)
// coroutine auto-cancels on onCleared()
}
}
}
viewModelScope e lifecycleScope — CoroutineScope integrati in Android che vengono cancellati all'evento del ciclo di vita corrispondente. Ciò elimina le fughe tramite le coroutine — lo scenario più comune nello sviluppo Android moderno.
Memory Profiler in Android Studio — lo strumento principale per il monitoraggio dell'heap. Mostra allocazioni in tempo reale, istantanee dell'heap e conteggi di oggetti per tipo. Consente di registrare un dump e analizzarlo in MAT (Memory Analyzer Tool) per trovare oggetti sospetti.
Eclipse MAT — un analizzatore di heap dump desktop. Dopo aver caricato un file HPROF da Android Studio, MAT costruisce un albero dominatore, mostra la retain size di ogni oggetto e offre un'analisi automatica delle fughe sospette tramite Leak Suspects Report.
Xcode Memory Graph — un debugger visivo dei retain cycle. Cliccando sul pulsante Memory Graph Debugger, Xcode ferma l'app, costruisce un grafo completo degli oggetti in memoria e evidenzia i retain cycle in rosso.
| Strumento | Piattaforma | Caratteristica |
|---|---|---|
| LeakCanary | Android | Rilevamento automatico delle fughe dopo destroy |
| Memory Profiler | Android Studio | Heap dump + allocazioni in tempo reale |
| Eclipse MAT | Android | Albero dominatore, Leak Suspects Report |
| Memory Graph | iOS (Xcode) | Visualizzatore di retain cycle |
Secondo Google I/O 2023, le app che utilizzano LeakCanary nelle build di debug riducono i crash legati alla memoria del 30–50% nei primi 2 mesi dall'adozione. Si consiglia di aggiungere LeakCanary durante la fase di onboarding del progetto.
Domande frequenti
Fuga — oggetti non accessibili al codice ma non raccolti dal GC a causa di riferimenti attivi. Gonfiore — l'app mantiene oggetti logicamente necessari ma in quantità eccessiva (ad esempio, una cache di 50 MB in un'app in esecuzione di 80 MB). Il gonfiore si risolve architetturalmente, la fuga attraverso una corretta gestione dei riferimenti.
LeakCanary utilizza ObjectWatcher — dopo onDestroy() di un'Activity, crea una WeakReference sull'Activity ed esegue il GC. Se la WeakReference non viene cancellata dopo 5 secondi, LeakCanary esegue un heap dump, analizza la catena di riferimento più breve dal GC Root all'oggetto e mostra lo stack esatto della fuga con il file e la riga di codice.
Bitmap occupa memoria al di fuori dell'heap Java nella memoria nativa (native heap). La dimensione di un Bitmap = larghezza × altezza × 4 byte (ARGB_8888). Una foto da 12 MP (4000×3000) occupa 48 MB. Android non sempre riesce a liberare tempestivamente la memoria nativa, quindi l'accumulo di più Bitmap porta a OOM anche con un heap Java sufficiente.
Retain cycle — una situazione in ARC in cui due oggetti mantengono riferimenti forti l'uno verso l'altro e il contatore dei riferimenti non raggiunge mai zero. Un esempio tipico: un ViewController con un riferimento forte a una closure, e la closure cattura self fortemente. Soluzione: utilizzate [weak self] o [unowned self] nelle closure.
La dimensione dell'heap dipende dal dispositivo e dalla versione di Android. Per i dispositivi meno recenti (API 15–24) — 64–128 MB. Per quelli moderni (API 25+) — 256–512 MB. Il valore esatto può essere ottenuto tramite ActivityManager.getMemoryClass(). Per app grandi (giochi, editor), largeHeap=true nel manifesto fornisce fino a 1 GB.
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