Tutto quello che dovete sapere sulle perdite di memoria in Android.
Uno dei vantaggi principali di Java, o per essere più precisi, della JVM (Java Virtual Machine), è il garbage collector (GC). Possiamo creare nuovi oggetti senza preoccuparci di liberarli dalla memoria. Il garbage collector si occuperà di allocare e liberare la memoria per noi.
Non esattamente! Possiamo evitare che il garbage collector liberi la memoria per noi se non capiamo bene come funziona il GC.
Scrivere un codice senza una buona comprensione di come funziona il GC potrebbe creare delle perdite di memoria nell’applicazione. Queste perdite possono influenzare la nostra applicazione sprecando memoria non rilasciata e alla fine causano eccezioni e ritardi di memoria.
- Che cos’è la perdita di memoria?
- Aspetta un minuto!!!🥴
- Heap & Stack
- Più sulla memoria Heap
- Ti sei mai chiesto quale sia la dimensione dell’heap per la tua applicazione?
- Come puoi controllare la dimensione dell’heap delle app per il tuo dispositivo?
- Come funziona nel mondo reale?
- Cosa succede quando i metodi terminano?
- Conclusione
- E l’Heap?
- Come funziona il garbage collector?
- Cosa succede quando il garbage collector viene eseguito?
- Quando e come avvengono le perdite di memoria?
- Come possiamo causare una perdita?
- Come possiamo causare una perdita usando il thread?
- Flusso regolare
Che cos’è la perdita di memoria?
Mancato rilascio di oggetti inutilizzati dalla memoria
Mancato rilascio di oggetti inutilizzati dalla memoria significa che ci sono oggetti inutilizzati nell’applicazione che il GC non può eliminare dalla memoria.
Quando il GC non può eliminare gli oggetti inutilizzati dalla memoria, siamo nei guai. L’unità di memoria che contiene gli oggetti inutilizzati sarà occupata fino alla fine dell’applicazione o (fino alla fine del metodo).
Fino alla fine del metodo? Sì, proprio così. Abbiamo due tipi di perdite, le perdite che occupano l’unità di memoria fino alla fine dell’applicazione e le perdite che occupano l’unità di memoria fino alla fine del metodo. Il primo è chiaro. Il secondo ha bisogno di maggiori chiarimenti. Facciamo un esempio per spiegarlo! Supponiamo di avere il metodo X. Il metodo X sta facendo qualche compito di lunga durata in background, e ci vorrà un minuto per finire. Inoltre, il metodo X sta tenendo oggetti inutilizzati mentre lo fa. In questo caso, l’unità di memoria sarà occupata, e gli oggetti inutilizzati non possono essere cancellati per un minuto fino alla fine del compito. Dopo la fine del metodo, il GC può cancellare gli oggetti inutilizzati e recuperare la memoria.
Questo è quello che voglio che sappiate per ora ci torneremo più tardi con del codice e della visualizzazione. SARÀ DIVERTENTE. 👹😜
Aspetta un minuto!!!🥴
Prima di saltare al contesto, iniziamo dalle basi.
RAM, o Random access memory, è la memoria nei dispositivi android o computer che viene utilizzata per memorizzare le applicazioni in esecuzione e i loro dati.
Vi spiegherò due personaggi principali della RAM, il primo è l’Heap, e il secondo è lo Stack. Passiamo alla parte divertente 🤩🍻.
Heap & Stack
Non la farò troppo lunga. Andiamo subito al punto, una breve descrizione, la Pila è usata per l’allocazione statica della memoria mentre l’Heap è usato per l’allocazione dinamica della memoria. Basta tenere a mente che sia l’Heap che lo Stack sono memorizzati nella RAM.
Più sulla memoria Heap
La memoria Heap di Java è usata dalla macchina virtuale per allocare gli oggetti. Ogni volta che si crea un oggetto, questo viene sempre creato nell’heap. Le macchine virtuali, come la JVM o la DVM, eseguono regolarmente la garbage collection (GC), rendendo la memoria heap di tutti gli oggetti che non sono più referenziati disponibile per allocazioni future.
Per fornire un’esperienza utente fluida, Android imposta un limite rigido alla dimensione heap per ogni applicazione in esecuzione. Il limite di dimensione dell’heap varia tra i dispositivi e si basa su quanta RAM ha un dispositivo. Se la tua applicazione colpisce questo limite di heap e cerca di allocare più memoria, riceverà un OutOfMemoryError
e terminerà.
Ti sei mai chiesto quale sia la dimensione dell’heap per la tua applicazione?
Scopriamolo insieme. In Android, abbiamo la Dalvik VM (DVM). La DVM è un’unica macchina virtuale Java ottimizzata per i dispositivi mobili. Ottimizza la macchina virtuale per la memoria, la durata della batteria e le prestazioni, ed è responsabile della distribuzione della quantità di memoria per ogni applicazione.
Parliamo di due linee nella DVM:
- dalvik.vm.heapgrowthlimit: Questa linea è basata su come Dalvik inizierà la dimensione dell’heap della vostra applicazione. È la dimensione heap predefinita per ogni applicazione. Il massimo che la vostra applicazione può raggiungere!
- dalvik.vm.heapsize: Questa linea rappresenta la dimensione massima dell’heap per un heap più grande. Puoi ottenere questo chiedendo ad Android un heap più grande nel manifesto della tua applicazione (android:largeHeap=”true”).
Non usare un heap più grande nella tua app. Fatelo SOLO se conoscete esattamente l’effetto collaterale di questo passo. Qui vi darò abbastanza informazioni per continuare la ricerca sull’argomento.
Qui c’è una tabella che mostra quale dimensione di heap avete in base alla RAM del vostro dispositivo:
+==========================+=========+=========+===================+
| DVM | 1GB RAM | 2GB RAM | 3GB RAM OR HIGHER |
+==========================+=========+=========+===================+
| DEFAULT(heapgrowthlimit) | 64m | 128m | 256m |
+--------------------------+---------+---------+-------------------+
| LARGE(heapsize) | 128m | 256m | 512m |
+--------------------------+---------+---------+-------------------+
Ricordate che più ram avete più alta sarà la dimensione di heap. Tieni a mente che non tutti i dispositivi con maggiore ram vanno oltre i 512m, fai una ricerca sul tuo dispositivo se il tuo dispositivo ha più di 3GB per vedere se la dimensione dell’heap è più grande di 512m.
Come puoi controllare la dimensione dell’heap delle app per il tuo dispositivo?
Usando l’ActivityManager. È possibile controllare la dimensione massima dell’heap in fase di esecuzione utilizzando i metodi getMemoryClass()
o getLargeMemoryClass()
(quando è abilitato un heap grande).
- getMemoryClass(): Restituisce la dimensione massima di heap predefinita.
- getLargeMemoryClass(): Restituisce la massima dimensione di heap disponibile dopo aver abilitato il flag large heap nel manifest.
ActivityManager am = getSystemService(ACTIVITY_SERVICE);
Log.d("XXX", "dalvik.vm.heapgrowthlimit: " + am.getMemoryClass());
Log.d("XXX", "dalvik.vm.heapsize: " + am.getLargeMemoryClass());
Come funziona nel mondo reale?
Useremo questa semplice applicazione per capire quando usiamo l’heap e quando lo stack.
L’immagine sottostante mostra una rappresentazione dell’heap e dello stack dell’applicazione e dove ogni oggetto punta e viene memorizzato quando eseguiamo l’applicazione.
Passeremo in rassegna l’esecuzione dell’app, fermeremo ogni linea, spiegheremo quando l’applicazione alloca gli oggetti e li memorizza nell’heap o nello stack. Vedremo anche quando l’applicazione rilascia gli oggetti dallo stack e dall’heap.
- Linea 1 – La JVM crea un blocco di memoria stack per il metodo main.
- Linea 2 – In questa linea, creiamo una variabile locale primitiva. La variabile sarà creata e memorizzata nella memoria dello stack del metodo principale.
- Linea 3 – Qui ho bisogno della vostra attenzione! In questa linea, creiamo un nuovo oggetto. L’oggetto viene creato nello stack del metodo principale e memorizzato nell’heap. Lo stack memorizza il riferimento, l’indirizzo di memoria oggetto-heap (puntatore), mentre l’heap memorizza l’oggetto originale.
- Linea 4 – La stessa della linea 3.
- Linea 5 – La JVM crea un blocco di memoria stack per il metodo foo.
- Linea 6 -Crea un nuovo oggetto. L’oggetto viene creato nella memoria dello stack del metodo foo, e noi memorizziamo nello stack l’indirizzo di memoria heap dell’oggetto che abbiamo creato nella linea 3. Il valore (indirizzo di memoria heap dell’oggetto nella linea 3) lo abbiamo passato nella linea 5. Tenete presente che Java passa sempre i riferimenti per valore.
- Linea 7 – Stiamo creando un nuovo oggetto. L’oggetto creato nello stack e puntato al pool di stringhe nell’heap.
- Linea 8 – Nella linea finale del metodo foo, il metodo termina. E gli oggetti saranno rilasciati dal blocco dello stack del metodo foo.
- Linea 9- Lo stesso della linea 8, Nella linea finale del metodo main, il metodo termina. E il blocco dello stack del metodo principale si libera.
Che dire della liberazione della memoria dall’heap? Ci arriveremo presto. Prendi un caffè☕️, e continua 😼.
Cosa succede quando i metodi terminano?
Ogni metodo ha il suo ambito. Quando il metodo viene terminato, gli oggetti vengono rilasciati e recuperati automaticamente dallo stack.
Nella figura 1, quando il metodo foo
termina. La memoria dello stack o il blocco dello stack del metodo foo sarà rilasciato e recuperato automaticamente.
L’utente avvia l’applicazione, apre la ThreadActivity, e aspetta sullo schermo fino alla fine del compito di download. L’utente ha aspettato 20 secondi. Perché 20 secondi? Perché questo è il tempo che il thread impiega per completare il compito.
Il compito è in esecuzione in background. L’utente ha aspettato per 20 secondi il completamento del compito di download. Quando il compito è finito, lo stack rilascia il blocco del metodo run().
Non ci sono riferimenti che tengano il DownloadTask. Il GC ha considerato l’oggetto DownladTask come un oggetto inutilizzato, e per questo motivo, il prossimo ciclo GC lo cancellerà dalla memoria heap.
Il GC cancella gli oggetti inutilizzati dall’heap. Ora, quando l’utente chiude l’attività. Il metodo main verrà rilasciato dallo stack, e nel prossimo ciclo del GC, il GC cancellerà il ThreadActivity dalla memoria dell’heap.
Perfetto!