Threads und Arbeitsspeicher

Jedes Mal, wenn Ihre Anwendung einen neuen Thread erstellt, muss das Betriebssystem Arbeitsspeicher dafür zuweisen. Threads gelten zwar im Vergleich zu vollständigen Prozessen als „leichtgewichtig“, aber ihre Speicherkosten sind nicht gleich null. Threads, die nicht ordnungsgemäß beendet werden, sind eine sehr häufige Ursache für Speicherlecks auf Android-Geräten.

Kosten eines Threads

Wenn ein Thread auf Android-Geräten erstellt wird, verbrauchen zwei Hauptkomponenten Arbeitsspeicher:

  1. Der native Stack: Die pthread Bibliothek weist einen Stack für den Thread zu um lokale Variablen und Aufruf-Frames zu speichern. Auf Android-Geräten ist das standardmäßig in der Regel 1 MB.
  2. Interne JVM-/Kernel-Strukturen: ART und der Linux-Kernel müssen Datenstrukturen wie pthread_internal_t und java.lang.Thread-Objekte zuweisen, um den Thread zu verfolgen und zu verwalten.

Stack-Arbeitsspeicher und mmap

Android weist den 1 MB großen nativen Stack mit mmap und den Flags MAP_ANONYMOUS | MAP_NORESERVE zu.

Das bedeutet, dass das Betriebssystem 1 MB virtuellen Arbeitsspeicheradressraum reserviert, aber nicht sofort 1 MB physischen RAM zuweist. Physischer Arbeitsspeicher wird nur zugewiesen (ausgelagert), wenn der Thread tatsächlich Code ausführt und Daten auf den Stack verschiebt. Ein Thread, der nichts tut, verbraucht nur sehr wenig physischen RAM für seinen Stack.

Praktische Übung: Threads, die nicht ordnungsgemäß beendet werden

Wir verwenden die Beispielanwendung MemoryLab , um zu beobachten, was passiert, wenn eine Anwendung Hunderte von inaktiven Threads erstellt.

1. Starten und Baseline

Starten Sie die App und lesen Sie die Baseline-Speicherauslastung aus:

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

Sehen Sie sich in der Ausgabe die Zeilen Stack und Native Heap an. Sie sind wahrscheinlich klein.

2. 100 Threads erstellen

Tippen Sie auf die Schaltfläche Create 100 Threads. Die Anwendung erstellt 100 neue Threads. Damit der Stack-Arbeitsspeicher tatsächlich in den physischen RAM ausgelagert wird (damit wir ihn in unseren Tools sehen können), weist jeder Thread ein kleines 10 KB großes Array auf seinem Stack zu und schreibt Daten hinein, bevor er mit dem Warten beginnt.

Führen Sie den Befehl „meminfo“ noch einmal aus, während die Threads aktiv sind:

adb shell dumpsys meminfo -s com.android.memorylab

Ergebnisse :

Vergleichen Sie die neue Ausgabe mit Ihrer Baseline. Sie werden feststellen, dass die Werte deutlich gestiegen sind, solange die Threads aktiv sind:

  • Stack: Um ein Megabyte oder mehr gestiegen. Dies entspricht den physischen Seiten, die für die 100 Thread-Stacks zugewiesen wurden (jeder mit mindestens 10 KB).
  • Native Heap / Private Other: Aufgrund der internen pthread-Strukturen gestiegen, die vom System für jeden Thread zugewiesen wurden.

Wenn Sie die Threads beenden möchten, tippen Sie auf die Schaltfläche Destroy All Threads.

3. Threads in Perfetto ansehen

Perfetto eignet sich hervorragend, um den Lebenszyklus und die Anzahl von Threads im Zeitverlauf zu verfolgen.

Ein Screenshot der Perfetto-Benutzeroberfläche mit einem Zähler-Track mit der Anzahl der aktiven Threads sowie Threads, die in der Memorylab-App gestartet und beendet werden

PerfettoSQL für die Thread-Analyse

Sie können die Trace-Daten abfragen, um genau zu ermitteln, wie viele Threads von der Anwendung erstellt wurden:

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-%';

Mit einer solchen Abfrage lässt sich ein Debug-Track mit einem Zähler erstellen, wie im Screenshot gezeigt.


← App-Code | ↑ Nach oben | Lokalität →