Каждый раз, когда ваше приложение создает новый поток, операционная система должна выделить память для его поддержки. Хотя потоки обычно считаются «легковесными» по сравнению с полноценными процессами, их затраты памяти не равны нулю. Утечка потоков — очень распространенный способ утечки памяти в Android.
Стоимость нити
При создании потока в Android память потребляют два основных компонента:
- Нативный стек : Библиотека
pthreadвыделяет стек для потока для хранения локальных переменных и кадров вызовов. На Android по умолчанию это обычно 1 МБ. - Внутренние структуры JVM/ядра : ART и ядро Linux должны выделять структуры данных (например, объекты
pthread_internal_tиjava.lang.Thread) для отслеживания и управления потоками.
Стек памяти и mmap
Важно понимать, что Android выделяет 1 МБ нативного стека, используя mmap с флагами MAP_ANONYMOUS | MAP_NORESERVE .
Это означает, что операционная система резервирует 1 МБ виртуального адресного пространства памяти, но не выделяет 1 МБ физической оперативной памяти немедленно. Физическая память выделяется (выгружается в подкачку) только тогда, когда поток фактически выполняет код и помещает данные в стек. Поток, который ничего не делает, будет потреблять очень мало физической оперативной памяти для своего стека.
Практическое упражнение: протекающие нити
Мы воспользуемся примером приложения MemoryLab , чтобы понаблюдать за тем, что происходит, когда приложение создает сотни простаивающих потоков.
1. Запуск и определение исходных показателей.
Запустите приложение и выполните базовое измерение памяти:
adb shell am start -W -n com.android.memorylab/.MainActivity
adb shell dumpsys meminfo -s com.android.memorylab
Обратите внимание на строки Stack и Native Heap в выходных данных. Скорее всего, они будут небольшими.
2. Создать 100 потоков.
Нажмите кнопку «Создать 100 потоков» . Приложение создаст 100 новых потоков. Чтобы гарантировать, что память стека действительно будет выгружена в физическую оперативную память (чтобы мы могли это увидеть в наших инструментах), каждый поток выделяет и записывает данные в небольшой массив размером 10 КБ в своем стеке, прежде чем начать ожидание.
Повторите команду meminfo, пока потоки активны:
adb shell dumpsys meminfo -s com.android.memorylab
Результаты:
Сравните новые результаты с базовыми. Вы заметите значительное увеличение производительности, пока потоки активны:
-
Stack: Увеличивается на мегабайт или более. Это представляет собой количество физических страниц, выделенных для 100 стеков потоков (каждый из которых затрагивает не менее 10 КБ). -
Native Heap/Private Other: Увеличено из-за внутренних структурpthread, выделяемых системой для каждого потока.
Для завершения процесса нажмите кнопку «Удалить все нити» .
3. Просмотр веток обсуждения в Perfetto
Perfetto отлично подходит для отслеживания жизненного цикла нитей и их количества с течением времени.

PerfettoSQL для анализа потоков
Вы можете запросить трассировку, чтобы точно подсчитать, сколько потоков было запущено приложением:
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-%';
Подобный запрос можно использовать для создания отладочной дорожки со счетчиком, как показано на скриншоте.
← Код приложения | ↑ Вверх | Местоположение →