La piattaforma Android si basa sul presupposto che la memoria libera è memoria sprecata. Cerca di utilizzare tutta la memoria disponibile in ogni momento. Ad esempio, il sistema mantiene le app in memoria dopo che sono state chiuse, in modo che l'utente possa tornare rapidamente a utilizzarle. Per questo motivo, i dispositivi Android spesso funzionano con pochissima memoria libera. La gestione della memoria è fondamentale per allocare correttamente la memoria tra i processi di sistema importanti e le numerose applicazioni utente.
Questa pagina illustra le nozioni di base su come Android alloca la memoria per il sistema e per le applicazioni utente. Spiega anche come il sistema operativo reagisce alle situazioni di memoria insufficiente.
Tipi di memoria
I dispositivi Android contengono tre tipi diversi di memoria: RAM, zRAM e spazio di archiviazione. Tieni presente che sia la CPU sia la GPU accedono alla stessa RAM.
Figura 1. Tipi di memoria: RAM, zRAM e spazio di archiviazione
La RAM è il tipo di memoria più veloce, ma di solito ha dimensioni limitate. I dispositivi di fascia alta in genere hanno la quantità di RAM più elevata.
zRAM è una partizione della RAM utilizzata per lo spazio di swap. Tutto viene compresso quando viene inserito in zRAM e poi decompresso quando viene copiato da zRAM. Le dimensioni di questa parte della RAM aumentano o diminuiscono man mano che le pagine vengono spostate in zRAM o rimosse da zRAM. I produttori di dispositivi possono impostare le dimensioni massime.
Lo spazio di archiviazione contiene tutti i dati persistenti, come il file system e il codice oggetto incluso per tutte le app, le librerie e la piattaforma. Lo spazio di archiviazione ha una capacità molto maggiore rispetto agli altri due tipi di memoria. Su Android, lo spazio di archiviazione non viene utilizzato per lo spazio di swap come in altre implementazioni di Linux, poiché la scrittura frequente può causare l'usura di questa memoria e ridurre la durata del supporto di archiviazione.
Pagine di memoria
La RAM è suddivisa in pagine. In genere, ogni pagina è di 4 KB di memoria.
Le pagine sono considerate libere o utilizzate. Le pagine libere sono RAM inutilizzata. Le pagine utilizzate sono RAM che il sistema utilizza attivamente e sono raggruppate nelle seguenti categorie:
- Memorizzate nella cache: memoria supportata da un file nello spazio di archiviazione (ad esempio, codice o file con mapping della memoria). Esistono due tipi di memoria memorizzata nella cache:
- Privata: di proprietà di un processo e non condivisa
- Pulita: copia non modificata di un file nello spazio di archiviazione, può essere eliminata da
kswapdper aumentare la memoria libera - Non pulita: copia modificata del file nello spazio di archiviazione; può essere spostata o compressa in zRAM da
kswapdper aumentare la memoria libera
- Pulita: copia non modificata di un file nello spazio di archiviazione, può essere eliminata da
- Condivisa: utilizzata da più processi
- Pulita: copia non modificata del file nello spazio di archiviazione, può essere eliminata da
kswapdper aumentare la memoria libera - Non pulita: copia modificata del file nello spazio di archiviazione; consente di riscrivere le modifiche nel file nello spazio di archiviazione per aumentare la memoria libera tramite
kswapd, o utilizzando esplicitamentemsync()omunmap()
- Pulita: copia non modificata del file nello spazio di archiviazione, può essere eliminata da
- Privata: di proprietà di un processo e non condivisa
- Anonima: memoria non supportata da un file nello spazio di archiviazione (ad esempio,
allocata da
mmap()con il flagMAP_ANONYMOUSimpostato)- Non pulita: può essere spostata/compressa in zRAM da
kswapdper aumentare la memoria libera
- Non pulita: può essere spostata/compressa in zRAM da
Le proporzioni di pagine libere e utilizzate variano nel tempo man mano che il sistema gestisce attivamente la RAM. I concetti introdotti in questa sezione sono fondamentali per gestire le situazioni di memoria insufficiente. La sezione successiva di questo documento li spiega in modo più dettagliato.
Gestione della memoria insufficiente
Android ha due meccanismi principali per gestire le situazioni di memoria insufficiente: il daemon di swap del kernel e il killer di memoria insufficiente.
Daemon di swap del kernel
Il daemon di swap del kernel (kswapd) fa parte del kernel Linux e converte la memoria utilizzata in memoria libera. Il daemon diventa attivo quando la memoria libera sul dispositivo è in esaurimento. Il kernel Linux mantiene le soglie di memoria libera bassa e alta.
Quando la memoria libera scende al di sotto della soglia bassa, kswapd inizia a recuperare la memoria. Una volta che la memoria libera raggiunge la soglia alta, kswapd smette di recuperare la memoria.
kswapd può recuperare le pagine pulite eliminandole perché sono supportate dallo spazio di archiviazione e non sono state modificate. Se un processo tenta di indirizzare una pagina pulita che è stata eliminata, il sistema copia la pagina dallo spazio di archiviazione alla RAM. Questa operazione è nota come paging su richiesta.
Figura 2. Pagina pulita, supportata dallo spazio di archiviazione, eliminata
kswapd può spostare le pagine modificate private memorizzate nella cache e le pagine modificate anonime in zRAM, dove vengono compresse. In questo modo, la memoria disponibile nella RAM (pagine libere) viene liberata. Se un processo tenta di toccare una pagina modificata in zRAM, la pagina viene decompressa e spostata di nuovo nella RAM. Se il processo associato a una pagina compressa viene interrotto, la pagina viene eliminata da zRAM.
Se la quantità di memoria libera scende al di sotto di una determinata soglia, il sistema inizia a interrompere i processi.
Figura 3. Pagina modificata spostata in zRAM e compressa
Killer di memoria insufficiente
Molte volte, kswapd non riesce a liberare memoria sufficiente per il sistema. In questo caso, il
sistema utilizza
onTrimMemory()
per notificare a un'app che la memoria è in esaurimento e che deve ridurre le
allocazioni. Se non è sufficiente, il kernel inizia a interrompere i processi per liberare memoria. Per farlo, utilizza il killer di memoria insufficiente (LMK).
Per decidere quale processo interrompere, LMK utilizza un punteggio di "memoria insufficiente" chiamato
oom_adj_score
per assegnare la priorità ai processi in esecuzione. I processi con un punteggio elevato vengono interrotti per primi. Le app in background vengono interrotte per prime e i processi di sistema vengono interrotti per ultimi. La tabella seguente elenca le categorie di punteggio LMK da quella più alta a quella più bassa. Gli elementi nella categoria con il punteggio più alto, nella prima riga, verranno interrotti per primi:
Figura 4. Processi Android, con punteggi elevati in alto e punteggi bassi in basso
Di seguito sono riportate le descrizioni delle varie categorie nella tabella sopra:
App in background: app eseguite in precedenza e non attualmente attive. LMK interromperà prima le app in background a partire da quella con il
oom_adj_scorepiù alto.App precedente: l'app in background utilizzata più di recente. L'app precedente ha una priorità più alta (un punteggio inferiore) rispetto alle app in background perché è più probabile che l'utente passi a questa app rispetto a una delle app in background.
App Home: è l'app di avvio. Se la interrompi, lo sfondo scomparirà.
Servizi: i servizi vengono avviati dalle applicazioni e possono includere la sincronizzazione o il caricamento nel cloud.
App percepibili: app non in primo piano che sono percepibili dall'utente in qualche modo, ad esempio l'esecuzione di un processo di ricerca che visualizza una piccola UI o l'ascolto di musica.
App in primo piano: l'app attualmente in uso. L'interruzione dell'app in primo piano sembra un arresto anomalo dell'applicazione, il che potrebbe indicare all'utente che qualcosa non va con il dispositivo.
Persistenti (servizi): si tratta di servizi principali per il dispositivo, come la telefonia e il Wi-Fi.
Sistema: processi di sistema. Quando questi processi vengono interrotti, lo smartphone potrebbe sembrare riavviarsi.
Nativi: processi di livello molto basso utilizzati dal sistema (ad esempio,
kswapd).
I produttori di dispositivi possono modificare il comportamento di LMK.
Calcolo del footprint della memoria
Il kernel tiene traccia di tutte le pagine di memoria nel sistema.
Figura 5. Pagine utilizzate da processi diversi
Quando determina la quantità di memoria utilizzata da un'app, il sistema deve tenere conto delle pagine condivise. Le app che accedono allo stesso servizio o alla stessa libreria condivideranno le pagine di memoria. Ad esempio, Google Play Services e un'app di gioco potrebbero condividere un servizio di localizzazione. In questo modo, è difficile determinare la quantità di memoria appartenente al servizio nel suo complesso rispetto a ogni applicazione.
Figura 6. Pagine condivise da due app (al centro)
Per determinare il footprint della memoria di un'applicazione, è possibile utilizzare una delle seguenti metriche:
- Dimensioni del set residente (RSS): il numero di pagine condivise e non condivise utilizzate dall'app
- Dimensioni del set proporzionale (PSS): il numero di pagine non condivise utilizzate dall'app e una distribuzione uniforme delle pagine condivise (ad esempio, se tre processi condividono 3 MB, ogni processo riceve 1 MB in PSS)
- Dimensioni del set univoco (USS): il numero di pagine non condivise utilizzate dall'app (le pagine condivise non sono incluse)
PSS è utile per il sistema operativo quando vuole sapere quanta memoria viene utilizzata da tutti i processi, poiché le pagine non vengono conteggiate più volte. Il calcolo di PSS richiede molto tempo perché il sistema deve determinare quali pagine sono condivise e da quanti processi. RSS non distingue tra pagine condivise e non condivise (il che rende il calcolo più veloce) ed è più adatto per monitorare le modifiche nell'allocazione della memoria.
Diagnosticare i Critical User Journey (CUJ) con un utilizzo elevato della memoria
Quando indaghi sui problemi di memoria, è utile identificare i Critical User Journey (CUJ) con un utilizzo elevato della memoria della tua app. Si tratta di flussi di lavoro specifici che richiedono intrinsecamente un'allocazione significativa della memoria. I CUJ con un utilizzo elevato della memoria comuni includono quanto segue:
- Caricamento e manipolazione di contenuti multimediali complessi o bitmap di grandi dimensioni.
- Hosting di istanze
WebView.
Se la tua app si basa su contenuti web, le istanze WebView possono spesso diventare un collo di bottiglia della memoria nascosto a causa della loro architettura multi-processo e dell'esecuzione di codice nativo. Per informazioni su diagnostiche specifiche, best practice del ciclo di vita e strumenti
personalizzati per i contenuti web, consulta Gestire e diagnosticare la memoria di WebView.
Risorse aggiuntive
- Panoramica della gestione della memoria
- Processi e ciclo di vita delle applicazioni
- Understanding Android memory usage - Google I/O presentation
- Android Memory and Games - Google I/O presentation
- Android low memory killer daemon
Consigliati per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Tempi di avvio dell'app