Чаты и память

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

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


← Код приложения | ↑ Наверх | Населенный пункт →