Allocazione di memoria tra processi

La piattaforma Android si basa sul presupposto che la memoria libera è memoria sprecata. Tenta di utilizzare tutta la memoria disponibile in qualsiasi momento. Ad esempio, il sistema mantiene le app in memoria dopo la chiusura, 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 molte applicazioni utente.

Questa pagina illustra le nozioni di base su come Android alloca la memoria per il sistema e per le applicazioni utente. Spiega inoltre come reagisce il sistema operativo in caso di memoria insufficiente.

Tipi di memoria

I dispositivi Android contengono tre diversi tipi di memoria: RAM, zRAM e spazio di archiviazione. Tieni presente che sia la CPU che la GPU accedono alla stessa RAM.

Tipi di memoria

Figura 1. Tipi di memoria: RAM, zRAM e spazio di archiviazione

  • La RAM è il tipo di memoria più veloce, ma di solito è di 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. Questa porzione di RAM aumenta o diminuisce di dimensioni man mano che le pagine vengono spostate dentro o fuori dalla zRAM. I produttori di dispositivi possono impostare le dimensioni massime.

  • L'archiviazione contiene tutti i dati persistenti, come il file system e il codice oggetto incluso per tutte le app, le librerie e la piattaforma. L'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 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 occupa 4 KB di memoria.

Le pagine vengono considerate senza costi o utilizzate. Le pagine libere sono RAM inutilizzata. Le pagine utilizzate sono la RAM che il sistema utilizza attivamente e sono raggruppate nelle seguenti categorie:

  • Memorizzato nella cache: memoria supportata da un file nello spazio di archiviazione (ad esempio codice o file mappati nella memoria). Esistono due tipi di memoria cache:
    • Privato: di proprietà di un processo e non condiviso
      • Pulito: copia non modificata di un file sullo spazio di archiviazione, può essere eliminata da kswapd per aumentare la memoria libera
      • Dirty: copia modificata del file nello spazio di archiviazione; può essere spostata o compressa in zRAM da kswapd per aumentare la memoria libera
    • Condiviso: utilizzato da più processi
      • Pulito: copia non modificata del file nello spazio di archiviazione, può essere eliminata da kswapd per aumentare la memoria libera
      • Dirty: copia modificata del file nello spazio di archiviazione; consente di riscrivere le modifiche nel file nello spazio di archiviazione per aumentare la memoria libera di kswapd o utilizzando esplicitamente msync() o munmap()
  • Anonimo: memoria non supportata da un file nello spazio di archiviazione (ad esempio, allocata da mmap() con il flag MAP_ANONYMOUS impostato)
    • Dirty: può essere spostato/compresso in zRAM di kswapd per aumentare la memoria libera

Le proporzioni delle 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 le spiega in modo più dettagliato.

Gestione della memoria ridotta

Android dispone di due meccanismi principali per gestire le situazioni di memoria insufficiente: il daemon di swap del kernel e il processo di eliminazione in caso di memoria insufficiente.

daemon di scambio 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 basse e alte. Quando la memoria libera scende sotto la soglia bassa, kswapd inizia a recuperare memoria. Quando la memoria libera raggiunge la soglia elevata, kswapd smette di recuperare 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 accedere a una pagina pulita che è stata eliminata, il sistema copia la pagina dallo spazio di archiviazione alla RAM. Questa operazione è nota come paging su richiesta.

Pagina pulita supportata dallo spazio di archiviazione eliminata

Figura 2. Pagina pulita, supportata dallo spazio di archiviazione, eliminata

kswapd può spostare le pagine private modificate e le pagine modificate anonime memorizzate nella cache in zRAM, dove vengono compresse. In questo modo, la memoria disponibile nella RAM (pagine libere) viene liberata. Se un processo tenta di accedere a 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 terminare i processi.

Pagina modificata spostata in zRAM e compressa

Figura 3. Pagina modificata spostata in zRAM e compressa

Low-memory killer

Molte volte, kswapd non riesce a liberare memoria sufficiente per il sistema. In questo caso, il sistema utilizza onTrimMemory() per comunicare a un'app che la memoria è in esaurimento e che deve ridurre le allocazioni. Se non è sufficiente, il kernel inizia a terminare i processi per liberare memoria. Per farlo, utilizza il processo di interruzione per memoria insufficiente (LMK).

Per decidere quale processo terminare, LMK utilizza un punteggio "out of memory" chiamato oom_adj_score per dare la priorità ai processi in esecuzione. I processi con un punteggio elevato vengono terminati per primi. Le app in background sono le prime a essere interrotte, mentre i processi di sistema sono gli ultimi a essere interrotti. La tabella seguente elenca le categorie di punteggio LMK dalla più alta alla più bassa. Gli elementi nella categoria con il punteggio più alto, nella prima riga, verranno eliminati per primi:

Processi Android, punteggi più alti in alto

Figura 4. Processi Android, con i punteggi più alti in alto e quelli più bassi in basso

Di seguito sono riportate le descrizioni delle varie categorie nella tabella precedente:

  • App in background: app eseguite in precedenza e non attualmente attive. LMK terminerà prima le app in background, a partire da quella con il valore di oom_adj_score più 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: questa è l'app di avvio. Se la chiudi, lo sfondo scompare.

  • Servizi: i servizi vengono avviati dalle applicazioni e possono includere la sincronizzazione o il caricamento sul cloud.

  • App percepibili: app non in primo piano che sono percepibili dall'utente in qualche modo, ad esempio l'esecuzione di una procedura di ricerca che mostra una piccola UI o l'ascolto di musica.

  • App in primo piano: l'app attualmente in uso. La chiusura dell'app in primo piano sembra un arresto anomalo dell'applicazione, il che potrebbe indicare all'utente che qualcosa non va con il dispositivo.

  • Permanenti (servizi): si tratta di servizi principali per il dispositivo, come telefonia e Wi-Fi.

  • Sistema: processi di sistema. Quando questi processi vengono interrotti, lo smartphone potrebbe sembrare riavviarsi.

  • Nativo: 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.

Pagine utilizzate da processi diversi

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 condividono 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 quanta memoria appartiene al servizio nel suo complesso rispetto a ogni applicazione.

Pagine condivise da due app

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 set uniche (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 PSS richiede molto tempo per essere calcolato 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 più veloce il calcolo) ed è più adatto per monitorare le modifiche nell'allocazione della memoria.

Risorse aggiuntive