Thread e memoria

Ogni volta che l'applicazione crea un nuovo thread, il sistema operativo deve allocare memoria per supportarlo. Sebbene i thread siano generalmente considerati "leggeri" rispetto ai processi completi, il loro costo in termini di memoria non è pari a zero. La perdita di thread è un modo molto comune di perdere memoria su Android.

Il costo di un thread

Quando viene creato un thread su Android, due componenti principali consumano memoria:

  1. Stack nativo: la libreria pthread alloca uno stack per il thread per archiviare le variabili locali e i frame di chiamata. Su Android, il valore predefinito è in genere 1 MB.
  2. Strutture JVM/kernel interne: ART e il kernel Linux devono allocare strutture di dati (come gli oggetti pthread_internal_t e java.lang.Thread) per monitorare e gestire il thread.

Memoria stack e mmap

È importante capire che Android alloca lo stack nativo da 1 MB utilizzando mmap con i flag MAP_ANONYMOUS | MAP_NORESERVE.

Ciò significa che il sistema operativo riserva 1 MB di spazio di indirizzi di memoria virtuale, ma non alloca immediatamente 1 MB di RAM fisica. La memoria fisica viene allocata (paged in) solo quando il thread esegue effettivamente il codice e inserisce i dati nello stack. Un thread che non esegue alcuna operazione consumerà pochissima RAM fisica per il suo stack.

Esercizio pratico: thread con leak

Utilizzeremo l'applicazione di esempio MemoryLab per osservare cosa succede quando un'applicazione crea centinaia di thread inattivi.

1. Lancio e riferimento

Avvia l'app ed esegui una lettura della memoria di base:

adb shell am start -W -n com.android.memorylab/.MainActivity
adb shell dumpsys meminfo -s com.android.memorylab

Osserva le righe Stack e Native Heap nell'output. Probabilmente saranno piccoli.

2. Crea 100 thread

Tocca il pulsante Crea 100 thread. L'applicazione genererà 100 nuovi thread. Per garantire che la memoria dello stack venga effettivamente impaginata nella RAM fisica (in modo da poterla vedere nei nostri strumenti), ogni thread alloca e scrive in un piccolo array da 10 kB sul proprio stack prima di iniziare l'attesa.

Esegui di nuovo il comando meminfo mentre i thread sono attivi:

adb shell dumpsys meminfo -s com.android.memorylab

I risultati:

Confronta il nuovo output con la base di riferimento. Noterai aumenti significativi mentre i thread sono attivi:

  • Stack: aumentato di un megabyte o più. Rappresenta le pagine fisiche allocate per i 100 stack di thread (ognuno dei quali tocca almeno 10 kB).
  • Native Heap / Private Other: aumentato a causa delle strutture interne di pthread allocate dal sistema per ogni thread.

Per eseguire la pulizia, tocca il pulsante Elimina tutti i thread.

3. Visualizzazione dei thread in Perfetto

Perfetto è eccellente per monitorare i cicli di vita e i conteggi dei thread nel tempo.

Uno screenshot della UI di Perfetto che mostra una traccia del contatore con il numero di thread attivi e i thread che iniziano ed escono nell'app memorylab

PerfettoSQL per l'analisi dei thread

Puoi eseguire una query sulla traccia per contare esattamente quanti thread sono stati generati dall'applicazione:

SELECT p.name AS process_name, COUNT(t.id) as thread_count
FROM thread t JOIN process p USING (upid)
WHERE p.name = 'com.android.memorylab' AND t.name LIKE 'LeakedThread-%';

Una query come questa può essere utilizzata per generare una traccia di debug con un contatore, come mostrato nello screenshot.


← Codice app | ↑ Su | Località →