Потоки и память

Каждый раз, когда ваше приложение создает новый поток, операционная система должна выделить память для его поддержки. Хотя потоки обычно считаются «легковесными» по сравнению с полноценными процессами, их затраты памяти не равны нулю. Утечка потоков — очень распространенный способ утечки памяти в Android.

Стоимость нити

При создании потока в Android память потребляют два основных компонента:

  1. Нативный стек : Библиотека pthread выделяет стек для потока для хранения локальных переменных и кадров вызовов. На Android по умолчанию это обычно 1 МБ.
  2. Внутренние структуры 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 отлично подходит для отслеживания жизненного цикла нитей и их количества с течением времени.

Скриншот пользовательского интерфейса Perfetto, показывающий счетчик с количеством активных потоков, а также потоков, запускающихся и завершающихся в приложении memorylab.

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

Подобный запрос можно использовать для создания отладочной дорожки со счетчиком, как показано на скриншоте.


← Код приложения | ↑ Вверх | Местоположение →