L’Android Runtime (ART) e la macchina virtuale Dalvik usano il paging e il memory-mapping (mmapping) per gestire la memoria. Questo significa che qualsiasi memoria che un’applicazione modifica, sia allocando nuovi oggetti che toccando pagine mmappate, rimane residente nella RAM e non può essere paginata fuori. L’unico modo per rilasciare la memoria da un’app è quello di rilasciare i riferimenti agli oggetti che l’app possiede, rendendo la memoria disponibile al garbage collector. Questo con un’eccezione: qualsiasi file mappato senza modifiche, come il codice, può essere impaginato fuori dalla RAM se il sistema vuole usare quella memoria altrove.
Questa pagina spiega come Android gestisce i processi delle app e l’allocazione della memoria. Per maggiori informazioni su come gestire la memoria in modo più efficiente nella tua app, vedi Gestire la memoria della tua app.
Garbage collection
Un ambiente di memoria gestito, come la macchina virtuale ART o Dalvik, tiene traccia di ogni assegnazione di memoria. Una volta che determina che un pezzo di memoria non è più utilizzato dal programma, lo libera di nuovo nell’heap, senza alcun intervento da parte del programmatore. Il meccanismo di recupero della memoria inutilizzata all’interno di un ambiente di memoria gestita è noto come garbage collection. La garbage collection ha due obiettivi: trovare oggetti di dati in un programma a cui non si può accedere in futuro; e recuperare le risorse utilizzate da quegli oggetti.
L’heap di memoria di Android è di tipo generazionale, il che significa che ci sono diversi bucket di allocazioni che tiene traccia, in base alla durata prevista e alla dimensione di un oggetto che viene allocato. Per esempio, gli oggetti allocati di recente appartengono alla generazione Young. Quando un oggetto rimane attivo abbastanza a lungo, può essere promosso ad una generazione più vecchia, seguita da una generazione permanente.
Ogni generazione di heap ha il suo limite superiore dedicato alla quantità di memoria che gli oggetti possono occupare. Ogni volta che una generazione inizia a riempirsi, il sistema esegue un evento di garbage collection nel tentativo di liberare la memoria. La durata della garbage collection dipende da quale generazione di oggetti sta raccogliendo e da quanti oggetti attivi ci sono in ogni generazione.
Anche se la garbage collection può essere abbastanza veloce, può comunque influenzare le prestazioni della tua app. Generalmente non controllate quando un evento di garbage collection si verifica dall’interno del vostro codice. Il sistema ha un insieme di criteri in esecuzione per determinare quando eseguire la garbage collection. Quando i criteri sono soddisfatti, il sistema ferma l’esecuzione del processo e inizia la garbage collection. Se la garbage collection si verifica nel mezzo di un ciclo di elaborazione intensivo come un’animazione o durante la riproduzione di musica, può aumentare il tempo di elaborazione. Questo aumento può potenzialmente spingere l’esecuzione del codice nella vostra app oltre la soglia raccomandata di 16ms per un rendering efficiente e fluido dei frame.
Inoltre, il vostro flusso di codice può eseguire tipi di lavoro che forzano gli eventi di garbage collection a verificarsi più spesso o li fanno durare più del normale. Per esempio, se allocate più oggetti nella parte più interna di un ciclo for durante ogni fotogramma di un’animazione alpha blending, potreste inquinare il vostro heap di memoria con molti oggetti. In questa circostanza, il garbage collector esegue più eventi di garbage collection e può degradare le prestazioni della tua applicazione.
Per informazioni più generali sulla garbage collection, vedi Garbage collection.
Condividere la memoria
Al fine di inserire tutto ciò di cui ha bisogno nella RAM, Android cerca di condividere le pagine RAM tra i processi. Può farlo nei seguenti modi:
- Ogni processo dell’applicazione è biforcato da un processo esistente chiamato Zygote. Il processo Zygote si avvia quando il sistema si avvia e carica il codice e le risorse comuni del framework (come i temi delle attività). Per avviare un nuovo processo di app, il sistema biforca il processo Zygote poi carica ed esegue il codice dell’app nel nuovo processo. Questo approccio permette alla maggior parte delle pagine di RAM allocate per il codice e le risorse del framework di essere condivise tra tutti i processi delle app.
- La maggior parte dei dati statici è mmappata in un processo. Questa tecnica permette ai dati di essere condivisi tra i processi, e permette anche di essere paginati fuori quando necessario. Esempi di dati statici includono: Il codice Dalvik (mettendolo in un file
.odex
precollegato per il mmapping diretto), le risorse delle app (progettando la tabella delle risorse per essere una struttura che può essere mmappata e allineando le voci zip dell’APK), ed elementi tradizionali del progetto come il codice nativo nei file.so
. - In molti punti, Android condivide la stessa RAM dinamica tra i processi utilizzando regioni di memoria condivisa esplicitamente allocate (con ashmem o gralloc). Per esempio, le superfici delle finestre usano la memoria condivisa tra l’app e il compositore dello schermo, e i buffer dei cursori usano la memoria condivisa tra il fornitore di contenuti e il client.
A causa dell’uso estensivo della memoria condivisa, determinare quanta memoria sta usando la vostra app richiede attenzione. Le tecniche per determinare correttamente l’uso della memoria della vostra app sono discusse in Investigare l’uso della RAM.
Allocare e recuperare la memoria dell’app
L’heap Dalvik è vincolato ad un singolo intervallo di memoria virtuale per ogni processo dell’app. Questo definisce la dimensione logica dell’heap, che può crescere se necessario, ma solo fino ad un limite che il sistema definisce per ogni app.
La dimensione logica dell’heap non è la stessa della quantità di memoria fisica usata dall’heap. Quando ispeziona l’heap della tua app, Android calcola un valore chiamato Proportional Set Size (PSS), che tiene conto delle pagine sporche e pulite che sono condivise con altri processi, ma solo in misura proporzionale a quante app condividono quella RAM. Questo totale (PSS) è ciò che il sistema considera la tua impronta di memoria fisica. Per maggiori informazioni su PSS, vedi la guida Investigare il tuo utilizzo della RAM.
L’heap Dalvik non compatta la dimensione logica dell’heap, il che significa che Android non deframmenta l’heap per chiudere lo spazio. Android può solo ridurre la dimensione logica dell’heap quando c’è spazio inutilizzato alla fine dell’heap. Tuttavia, il sistema può ancora ridurre la memoria fisica utilizzata dall’heap. Dopo la garbage collection, Dalvik percorre l’heap e trova le pagine inutilizzate, poi restituisce quelle pagine al kernel usando madvise. Così, le allocazioni e le deallocazioni accoppiate di grandi blocchi dovrebbero risultare nel recupero di tutta (o quasi tutta) la memoria fisica usata. Tuttavia, recuperare la memoria da piccole allocazioni può essere molto meno efficiente perché la pagina usata per una piccola allocazione può essere ancora condivisa con qualcos’altro che non è stato ancora liberato.
Ridurre la memoria delle app
Per mantenere un ambiente multi-tasking funzionale, Android imposta un limite rigido alla dimensione dell’heap per ogni app. L’esatto limite di dimensione dell’heap varia tra i dispositivi in base alla quantità di RAM che il dispositivo ha a disposizione. Se la tua app ha raggiunto la capacità dell’heap e cerca di allocare altra memoria, può ricevere un OutOfMemoryError
.
In alcuni casi, potresti voler interrogare il sistema per determinare esattamente quanto spazio heap hai a disposizione sul dispositivo corrente, ad esempio per determinare quanti dati è sicuro tenere in una cache. Puoi interrogare il sistema per questo dato chiamando getMemoryClass()
. Questo metodo restituisce un numero intero che indica il numero di megabyte disponibili per l’heap della tua app.
Cambio di app
Quando gli utenti passano da un’app all’altra, Android mantiene le app che non sono in primo piano – cioè non visibili all’utente o che eseguono un servizio in primo piano come la riproduzione della musica – in una cache. Per esempio, quando un utente lancia per la prima volta un’app, viene creato un processo per essa; ma quando l’utente lascia l’app, quel processo non esce. Il sistema mantiene il processo nella cache. Se l’utente in seguito ritorna all’app, il sistema riutilizza il processo, rendendo così la commutazione dell’app più veloce.
Se la vostra app ha un processo nella cache e trattiene risorse di cui attualmente non ha bisogno, allora la vostra app – anche mentre l’utente non la sta usando – influenza le prestazioni generali del sistema. Quando il sistema è a corto di risorse come la memoria, uccide i processi nella cache. Il sistema tiene anche conto dei processi che trattengono la maggior parte della memoria e può terminarli per liberare la RAM.
Nota: meno memoria consuma la vostra app mentre è nella cache, maggiori sono le sue possibilità di non essere uccisa e di poter riprendere rapidamente. Tuttavia, a seconda dei requisiti istantanei del sistema, è possibile che i processi memorizzati nella cache vengano terminati in qualsiasi momento, indipendentemente dal loro utilizzo di risorse.
Per maggiori informazioni su come i processi vengono memorizzati nella cache mentre non sono in esecuzione in primo piano e su come Android decide quali possono essere uccisi, vedere la guida Processi e thread.