Allocazione di memoria tra processi

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.

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 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 kswapd per aumentare la memoria libera
      • Non pulita: copia modificata del file nello spazio di archiviazione; può essere spostata o compressa in zRAM da kswapd per aumentare la memoria libera
    • Condivisa: utilizzata da più processi
      • Pulita: copia non modificata del file nello spazio di archiviazione, può essere eliminata da kswapd per 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 esplicitamente msync() o munmap()
  • Anonima: memoria non supportata da un file nello spazio di archiviazione (ad esempio, allocata da mmap() con il flag MAP_ANONYMOUS impostato)
    • Non pulita: può essere spostata/compressa in zRAM da kswapd per aumentare la memoria libera

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.

Pagina pulita supportata dallo spazio di archiviazione eliminata

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.

Pagina modificata spostata in zRAM e compressa

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:

Processi Android, punteggi più alti in alto

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_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: è 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.

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 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.

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 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