Allocazione di memoria tra processi

La piattaforma Android si basa sul presupposto che la memoria libera viene sprecata. Prova a utilizzare sempre tutta la memoria disponibile. Ad esempio, il sistema conserva le app in memoria dopo la chiusura in modo che l'utente possa accedervi rapidamente. Per questo motivo, i dispositivi Android spesso vengono eseguiti con pochissima memoria libera. La gestione della memoria è fondamentale per allocare correttamente la memoria tra importanti processi di sistema e molte applicazioni utente.

Questa pagina illustra le nozioni di base della modalità di allocazione della memoria da parte di Android per il sistema e per le applicazioni utente. Spiega inoltre come reagisce il sistema operativo alle situazioni in cui la 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 sia 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 ha dimensioni limitate. I dispositivi di fascia alta hanno in genere la maggiore quantità di RAM.

  • zRAM è una partizione della RAM utilizzata per lo spazio di swap. Tutto viene compresso quando viene inserito in una zRAM, quindi decompresso quando viene copiato dalla zRAM. Le dimensioni di questa porzione di RAM aumentano o si riducono man mano che le pagine vengono spostate o eliminate dalla zRAM. I produttori di dispositivi possono impostare la dimensione massima.

  • Storage contiene tutti i dati permanenti come il file system e il codice oggetto incluso per tutte le app, le librerie e la piattaforma. Storage ha molta più capacità rispetto agli altri due tipi di memoria. Su Android, lo spazio di archiviazione non viene utilizzato per lo spazio di scambio come lo è in altre implementazioni Linux, poiché scritture frequenti possono causare usura di questa memoria e ridurre la durata del supporto di archiviazione.

Pagine della memoria

La RAM è suddivisa in pagine. In genere ogni pagina ha una memoria di 4 kB.

Le pagine sono considerate senza costi o usate. Le pagine senza costi sono RAM non utilizzata. Le pagine usate sono RAM utilizzate attivamente dal sistema e sono raggruppate nelle seguenti categorie:

  • Memorizzata nella cache: memoria supportata da un file nello spazio di archiviazione (ad esempio codice o file mappati in memoria). Esistono due tipi di memoria memorizzata nella cache:
    • Privata: di proprietà di un unico processo e non condivisa.
      • Pulisci: copia non modificata di un file nello spazio di archiviazione, può essere eliminata da kswapd per aumentare la memoria disponibile
      • Dirty: copia modificata del file nello spazio di archiviazione; può essere spostata o compressa nella zRAM da kswapd per aumentare la memoria disponibile
    • Condiviso: utilizzato da più processi.
      • Pulisci: copia non modificata del file nello spazio di archiviazione, può essere eliminata entro il giorno kswapd per aumentare la memoria disponibile
      • Dirty: copia modificata del file nello spazio di archiviazione; consente di riscrivere le modifiche nel file nello spazio di archiviazione per aumentare la memoria disponibile di kswapd oppure utilizzare 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

La proporzione di pagine senza costi e utilizzate varia nel tempo poiché il sistema gestisce attivamente la RAM. I concetti presentati in questa sezione sono fondamentali per gestire le situazioni in cui la memoria è insufficiente. La prossima sezione del documento li spiega in modo più dettagliato.

Gestione della memoria insufficiente

Android ha due meccanismi principali per gestire le situazioni in cui la memoria è insufficiente: il daemon swap del kernel e il killer con memoria ridotta.

daemon dello scambio del kernel

Il daemon di scambio del kernel (kswapd) fa parte del kernel Linux e converte la memoria utilizzata in memoria libera. Il daemon si attiva quando la memoria disponibile sul dispositivo è in esaurimento. Il kernel Linux mantiene soglie di memoria libera basse e alte. Quando la memoria libera scende al di sotto della soglia minima, kswapd inizia a recuperarla. Quando la memoria libera raggiunge la soglia alta, kswapd interrompe il recupero della memoria.

kswapd può recuperare pagine pulite eliminandole perché sono supportate dallo spazio di archiviazione e non sono state modificate. Se un processo tenta di risolvere una pagina pulita che è stata eliminata, il sistema copia la pagina dallo spazio di archiviazione alla RAM. Questa operazione è nota come paging della domanda.

Pagina pulita supportata dallo spazio di archiviazione eliminato

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

kswapd può spostare su zRAM le pagine private e le pagine sporche anonime memorizzate nella cache, dove vengono compresse. In questo modo viene liberata memoria disponibile nella RAM (pagine libere). Se un processo cerca di toccare una pagina con dati sporchi 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 dirty spostata su zRAM e compressa

Figura 3. Pagina dirty spostata su zRAM e compressa

killer a memoria ridotta

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 dovrebbe ridurre le allocazioni. Se ciò non è sufficiente, il kernel inizia ad terminare i processi per liberare memoria. A questo scopo, utilizza l'LMK (low-memory killer).

Per decidere quale processo terminare, LMK utilizza un punteggio di "memoria esaurita" chiamato oom_adj_score per assegnare la priorità ai processi in esecuzione. I processi con un punteggio elevato vengono terminati per primi. Le app in background vengono prima interrotte, mentre i processi di sistema vengono arrestati gli ultimi. La seguente tabella elenca le categorie di punteggio LMK dal più alto al più basso. Gli elementi nella categoria con il punteggio più alto, nella prima riga, verranno eliminati per primi:

Processi Android, punteggi migliori in cima

Figura 4. Processi di Android, con i punteggi alti nella parte superiore e i punteggi bassi nella parte inferiore

Queste sono le descrizioni per le varie categorie riportate nella tabella precedente:

  • App in background: le app che sono state eseguite in precedenza e che al momento non sono attive. LMK termina prima le app in background a partire da quella con il valore 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 all'app rispetto a una delle app in background.

  • App Home. Questa è l'app Avvio app. Se la elimini, lo sfondo scomparirà.

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

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

  • App in primo piano: l'app attualmente in uso. L'interruzione dell'app in primo piano si presenta come un arresto anomalo dell'applicazione, che potrebbe indicare all'utente che si sta verificando un problema con il dispositivo.

  • Permanenti (servizi): si tratta dei servizi principali del dispositivo, ad esempio telefonia e Wi-Fi.

  • Sistema: processi di sistema. Man mano che questi processi vengono interrotti, lo smartphone potrebbe riavviarsi.

  • Nativo: processi di livello molto basso utilizzati dal sistema (ad esempio, kswapd).

I produttori di dispositivi possono modificare il comportamento di LMK.

Calcolo dell'impronta di memoria

Il kernel tiene traccia di tutte le pagine di memoria del sistema.

Pagine utilizzate da diversi processi

Figura 5. Pagine utilizzate da diversi processi

Per determinare 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 raccolta condivideranno pagine di memoria. Ad esempio, Google Play Services e un'app di gioco potrebbero condividere un servizio di geolocalizzazione. Ciò rende difficile determinare quanta memoria appartiene al servizio su larga scala rispetto a ogni applicazione.

Pagine condivise da due app

Figura 6. Pagine condivise da due app (al centro)

Per determinare l'ingombro della memoria per un'applicazione, è possibile utilizzare una qualsiasi delle seguenti metriche:

  • Dimensioni del set di residenza (RSS): il numero di pagine condivise e non condivise utilizzate dall'app
  • Dimensioni set proporzionali (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 per quanti processi. RSS non fa distinzione tra pagine condivise e non condivise, il che rende il calcolo più rapido ed è più adatto per monitorare le variazioni nell'allocazione della memoria.

Risorse aggiuntive