Каждый раз, когда ваше приложение создает новый поток, операционная система должна выделять память для его поддержки. Хотя потоки обычно считаются "легковесными" по сравнению с полноценными процессами, они все равно потребляют память. Утечка потоков – очень распространенная причина утечки памяти в 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-%';
Запрос, подобный этому, можно использовать для создания отладочного трека со счетчиком, как показано на скриншоте.
← Код приложения | ↑ Наверх | Населенный пункт →