Memory leak e gonfiore — cosa sono, cause e come evitarli

Autore: IT Sectr Pubblicato: 2026-07-28 Tempo di lettura: 10 min

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

  • Raggiungibilità GC — un oggetto non viene rimosso se esiste un riferimento attivo dal set radice
  • Riferimenti statici ad Activity o Context — la causa più comune di fughe in Android
  • LeakCanary — lo strumento standard per il rilevamento automatico delle fughe in Android
  • WeakReference — una soluzione per i riferimenti che non devono ostacolare la garbage collection
  • Componenti Lifecycle-aware cancellano automaticamente le sottoscrizioni alla distruzione della view

Cos'è una fuga di memoria e il gonfiore dell'app?

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.

CaratteristicaAndroidiOS
Limite heap64–512 MB (dipende dal dispositivo)Implicito (sistema)
Garbage collectionART (Concorrente, Compatto)ARC (Conteggio automatico dei riferimenti)
Meccanismo di fugaRiferimenti GC RootRetain cycle (cicli di riferimenti forti)
RisultatoOutOfMemoryErrorAvviso 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.

Pattern comuni di fuga di memoria in Android e iOS

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.

kotlin
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.

  • Handler con ritardo — se l'Activity viene distrutta ma Handler.postDelayed non è ancora stato eseguito, l'Activity fuoriesce
  • Thread e AsyncTask — durante la rotazione dello schermo, l'Activity viene ricreata mentre il vecchio Thread continua a mantenere un riferimento alla vecchia Activity
  • Retrofit/Callback — un Callback anonimo mantiene un riferimento al presenter o al fragment
  • Osservatori — sottoscrizioni LiveData o RxJava senza cancellazione a onDestroy

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.

Come rilevare le fughe di memoria?

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.

kotlin
// 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.

Strategie di prevenzione

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.

kotlin
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.

  • Non utilizzate riferimenti statici a Context, Activity, View o Fragment
  • Cancellate tutte le sottoscrizioni RxJava in disposeBag / CompositeDisposable a onDestroy
  • Utilizzate [weak self] / [unowned self] nelle closure iOS per prevenire i retain cycle
  • Verificate Bitmap e oggetti grandi — devono essere riciclati o nullificati

Strumenti di profilazione della memoria

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.

StrumentoPiattaformaCaratteristica
LeakCanaryAndroidRilevamento automatico delle fughe dopo destroy
Memory ProfilerAndroid StudioHeap dump + allocazioni in tempo reale
Eclipse MATAndroidAlbero dominatore, Leak Suspects Report
Memory GraphiOS (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

Qual è la differenza tra una fuga di memoria e il gonfiore?

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.

Come trova LeakCanary le fughe?

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.

Perché Bitmap causa spesso OutOfMemoryError?

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.

Cos'è un retain cycle in iOS?

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.

Qual è la dimensione massima dell'heap su Android?

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

  • Fuga di memoria — un oggetto non raccolto dal GC a causa di un riferimento attivo dal set radice; gonfiore — consumo eccessivo di memoria senza fughe esplicite
  • Riferimenti statici ad Activity, Context o View — la causa numero uno di fughe in Android; soluzione — WeakReference o Application Context
  • Classi anonime e lambda trattengono implicitamente un riferimento alla classe esterna; i callback non cancellati sono la seconda causa più comune
  • LeakCanary — lo standard per il rilevamento automatico delle fughe in Android; l'integrazione richiede 5 minuti e riduce il tasso di crash del 30–50%
  • lifecycleScope e viewModelScope cancellano automaticamente le coroutine alla distruzione, eliminando un'intera classe di fughe
  • Retain cycle in iOS si risolvono con weak/unowned self nelle closure e nei delegati
  • Profilate la memoria almeno una volta per sprint — un heap dump con MAT o Memory Graph dovrebbe diventare parte della revisione del codice

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.

Discuti il progetto

Leggi anche