Heap Dump (dump dell'heap) è un'istantanea della memoria dinamica di un'applicazione contenente informazioni complete su tutti gli oggetti vivi: le loro classi, dimensioni, riferimenti reciproci e accessibilità dalle radici GC. Heap Dump è lo strumento principale per analizzare le perdite di memoria e ottimizzare il consumo di risorse. Secondo Android Developers, l'analisi degli heap dump consente di rilevare fino al 95% delle perdite di memoria, inclusi riferimenti ciclici, listener dimenticati e riferimenti statici non rilasciati.
Punti Chiave
Heap dump è un dump completo dell'heap della macchina virtuale — l'area di memoria in cui risiedono tutti gli oggetti creati dinamicamente. In Java e Kotlin è l'heap Dalvik/ART su Android, in Swift e Objective-C è l'heap gestito da ARC su iOS. Un heap dump cattura ogni oggetto, la sua classe, dimensione, campi, riferimenti ad altri oggetti e flag di accessibilità dalle radici GC (variabili di stack, campi statici, riferimenti JNI).
Lo scopo principale di un heap dump è il rilevamento di perdite di memoria. Una perdita si verifica quando un'applicazione continua a mantenere riferimenti a oggetti che non sono più necessari, impedendo la loro raccolta da parte del garbage collector. Cause tipiche: listener di eventi non deregistrati alla distruzione di un'activity; singleton con riferimenti al contesto; closure che catturano self; collezioni statiche in cui i dati vengono aggiunti senza rimozione. Un heap dump fornisce un quadro preciso: quali oggetti sono “vivi”, quali sono superflui e chi li sta referenziando.
Secondo Google I/O, oltre il 60% dei rapporti di crash delle applicazioni Android sono correlati a OutOfMemoryError, e nell'80% dei casi la causa principale è una perdita di memoria rilevabile tramite heap dump. Per le applicazioni iOS la situazione è simile: le perdite dovute a retain cycle sono una delle cause più comuni di crash, identificate tramite lo strumento Allocations in Xcode.
Un heap dump dovrebbe essere eseguito quando si presentano i seguenti sintomi: l'applicazione consuma memoria in modo lineare durante azioni ripetitive (navigazione avanti e indietro tra schermate); dopo la chiusura di una schermata, la memoria non torna al livello base; si verificano OutOfMemoryError o avvisi di memoria su iOS; l'applicazione termina per superamento del limite di memoria. La raccolta regolare di heap dump fa parte del protocollo di cultura ingegneristica in grandi progetti mobili come Instagram e Spotify.
Android Studio fornisce Memory Profiler — uno strumento integrato per catturare heap dump in tempo reale. Accessibile tramite View → Tool Windows → Profiler. Dopo aver avviato l'applicazione, selezionare la sessione, andare alla scheda Memory e fare clic su Dump Java Heap. Android Studio sospende l'applicazione, esegue un dump dell'heap ART e carica il risultato per l'analisi. Il file di dump è in formato .hprof — lo standard HPROF compatibile con la maggior parte degli analizzatori di memoria.
Dopo il caricamento del dump, Android Studio visualizza una tabella di oggetti con colonne: Allocations (numero di istanze), Native Size (memoria fuori dall'heap ART), Shallow Size (memoria dell'oggetto stesso), Retained Size (memoria dell'oggetto incluso l'intero sottografo). Il filtraggio per nome classe, l'ordinamento per retained size e la ricerca per pacchetti consentono di trovare rapidamente le aree problematiche.
// Perdita tipica — un listener non deregistrato in onDestroy
class MainActivity : AppCompatActivity() {
private val sensorManager by lazy {
getSystemService(SENSOR_SERVICE) as SensorManager
}
private val listener = MySensorListener()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sensorManager.registerListener(listener,
sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT),
SensorManager.SENSOR_DELAY_NORMAL)
}
override fun onDestroy() {
super.onDestroy()
// ❌ Manca sensorManager.unregisterListener(listener)
// → L'Activity non verrà raccolta dal GC, l'heap dump mostrerà la perdita
}
}
La scheda Dominator Tree mostra gli oggetti che trattengono la maggior quantità di memoria. Se un oggetto viene rimosso dal dominator tree, tutta la memoria che trattiene diventa disponibile per la raccolta. Questo è uno strumento chiave: invece di scansionare migliaia di oggetti, ci si concentra su 10–20 che controllano l'80–90% della memoria. Secondo Google, l'analisi del dominator tree è il modo più efficace per trovare un punto di perdita, riducendo il tempo di analisi da ore a minuti.
Xcode Instruments fornisce due strumenti per lavorare con gli heap dump: Allocations — cattura di dump dell'heap con grafico del consumo in tempo reale; Leaks — ricerca automatica di perdite tramite analisi dei retain cycle. Allocations mostra tutti gli oggetti nell'heap, la loro dimensione, il numero di creazioni (allocations) e deallocazioni (deallocations). La differenza tra il numero di creazioni e deallocazioni per una classe specifica indica una potenziale perdita.
La cattura di un heap dump in Allocations viene eseguita con il pulsante Snapshot Memory — lo strumento sospende l'applicazione e scatta un dump completo. Successivamente, sono disponibili le viste standard: elenco di oggetti per classe, albero di chiamate (call tree) per ogni oggetto e un generatore di report. A differenza di Android Studio, Xcode non utilizza .hprof, ma memorizza i dati nel proprio formato .trace compatibile con Instruments.
// Perdita iOS tipica — retain cycle tramite una closure
class NetworkManager {
var onComplete: ((Data) -> Void)?
func startRequest() {
// ❌ La closure cattura self — retain cycle
onComplete = { data in
self.process(data)
}
}
func process(_ data: Data) {}
}
Lo strumento Leaks rileva automaticamente retain cycle e perdite tramite l'analisi del grafo dei riferimenti. Marca gli oggetti che perdono con un'icona viola e mostra il percorso verso la radice (GC root). Per eliminare un retain cycle, è sufficiente aggiungere [weak self] o [unowned self] nella cattura della closure. L'esecuzione regolare dello strumento Leaks è un passaggio obbligatorio della pipeline CI nei team che utilizzano Swift per lo sviluppo iOS.
// Correzione — riferimento debole a self
onComplete = { [weak self] data in
guard let self else { return }
self.process(data)
}
Per un'analisi corretta di un heap dump è necessario comprendere tre metriche chiave. Shallow size è la quantità di memoria occupata direttamente dall'oggetto: i suoi campi, l'intestazione (header) e l'allineamento. Per un tipico oggetto Java/Kotlin, la shallow size è di 16–40 byte. Retained size è la shallow size dell'oggetto più la somma della shallow size di tutti gli oggetti che sono accessibili solo attraverso questo oggetto (cioè diventerebbero spazzatura se venisse rimosso). La retained size mostra l'impatto reale dell'oggetto sul consumo di memoria.
| Metrica | Descrizione | Esempio |
|---|---|---|
| Shallow size | Dimensione dell'oggetto stesso in byte | Bitmap (100×100) = 40.016 B |
| Retained size | Shallow size + tutto ciò che trattiene | Activity con View Tree = 2–5 MB |
| Deep size | Retained size + oggetti annidati da altri grafi | ScrollView con adapter = 10–50 MB |
Dominator tree è una struttura in cui ogni oggetto riferisce il suo “dominator” — l'oggetto che controlla la sua accessibilità. Se il dominator viene rimosso, tutti gli oggetti del suo sottoalbero diventano spazzatura. L'analisi del dominator tree è il modo più rapido per scoprire quale oggetto trattiene più memoria. Secondo Eclipse MAT (Memory Analyzer Tool), il 90% delle perdite viene rilevato esaminando la top-20 del dominator tree in 5 minuti.
Il processo di analisi di una perdita tramite heap dump consiste in diversi passaggi. Passo 1: eseguire l'azione che dovrebbe liberare memoria (chiudere la schermata, terminare l'operazione). Passo 2: chiamare il GC ed eseguire un heap dump. Passo 3: trovare gli oggetti che avrebbero dovuto essere distrutti. Passo 4: per l'oggetto sospetto, eseguire Path to GC Roots — la catena di riferimenti che mantiene vivo l'oggetto. L'ultimo riferimento nella catena è la causa della perdita.
La funzione Path to GC Roots è disponibile in Android Studio Profiler, Eclipse MAT e Xcode Instruments. Mostra la catena più breve di riferimenti da una radice GC all'oggetto problematico. Escludendo i riferimenti deboli (weak) e morbidi (soft), si ottengono solo quelli forti (strong) — quelli che impediscono effettivamente la raccolta. Secondo Square Engineering, il 70% delle perdite nelle applicazioni Android è causato da solo due modelli: riferimenti statici ad Activity o Context e listener registrati ma non deregistrati.
// Esempio di perdita tramite riferimento statico
object AppCache {
private val cache = mutableMapOf<String, Any>()
fun storeActivityReference(activity: Activity) {
cache["current_activity"] = activity // ❌ Perdita!
}
}
// Correzione: riferimento debole
object AppCacheFixed {
private val cache = mutableMapOf<String, WeakReference<Any>>()
}
La tecnica della modalità di confronto è uno dei metodi più efficaci per trovare perdite. Eseguire un heap dump prima e dopo un'azione ripetitiva. Confrontare il numero di istanze delle classi chiave: se il numero di Activity è aumentato nonostante tutte le activity siano state chiuse — è una perdita. Android Studio ed Eclipse MAT supportano il confronto automatico dei dump con evidenziazione delle differenze. Secondo Google, il confronto dei dump consente di trovare perdite invisibili in un'analisi singola grazie all'effetto di accumulo.
Basandosi sull'analisi degli heap dump in progetti reali, sono state sviluppate pratiche comprovate di ottimizzazione della memoria. Utilizzare WeakReference per cache, callback e riferimenti al contesto in oggetti di lunga durata. Deregistrare i listener in onPause/onDestroy per Android e deinit per iOS. Evitare grandi collezioni statiche — se necessarie, utilizzare LruCache con limite di dimensione. Ottimizzare i Bitmap: caricare le immagini con il corretto inSampleSize, utilizzare Glide o Picasso con cache su disco.
Includere la cattura regolare di heap dump nella propria pipeline CI. Impostare un'attività che esegua test UI strumentati, svolga gli scenari utente chiave e confronti l'heap dump con una baseline. Se la retained size cresce più del 5% rispetto alla baseline, la build viene contrassegnata come regressione. Questo approccio è praticato in Airbnb, Uber e altre aziende con elevati requisiti di qualità. Secondo Uber Engineering, l'implementazione dell'analisi automatica degli heap dump in CI ha ridotto i bug relativi alla memoria del 70% in un trimestre.
// Esempio di attività Gradle per heap dump automatico in CI
task profileMemory(type: Exec) {
commandLine 'adb', 'shell',
'am start -n com.example/.MainActivity'
// In attesa del caricamento
doLast {
exec { commandLine 'adb', 'shell',
'am broadcast -a com.example.DUMP_HEAP' }
}
}
Domande Frequenti
Shallow size è la dimensione dell'oggetto stesso (campi + intestazione). Retained size è la dimensione dell'oggetto più tutti gli oggetti che diventerebbero spazzatura se venisse rimosso. La retained size è il principale indicatore dell'impatto di un oggetto sul consumo di memoria.
Tramite Android Studio Profiler, selezionare il dispositivo e il processo, fare clic su Dump Java Heap. In alternativa, tramite riga di comando: adb shell am dumpheap PID /sdcard/dump.hprof, poi adb pull.
Un heap dump include tutti gli oggetti vivi. Se l'applicazione utilizza cache, Bitmap o elabora grandi dati, il dump può raggiungere centinaia di megabyte. Filtrare per classi o utilizzare Eclipse MAT per caricare solo l'indice.
Sì, utilizzare Eclipse MAT (Memory Analyzer Tool) — uno strumento gratuito per analizzare file .hprof. Supporta dominator tree, path to GC roots, confronto di dump e rilevamento automatico di perdite tramite Leak Suspects Report.
Il dump stesso — sì, perché la raccolta del dump sospende tutti i thread (stop-the-world). Senza dump — no. Eseguire i dump in ambienti controllati (banco di prova, CI), non in produzione.
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