Как проверить утечку памяти приложения

Обновлено: 05.07.2024

Также приглашаем поучаствовать в открытом вебинаре на тему «Методы LINQ, которые сделают всё за вас» — на нем участники обсудят шесть представителей семейства технологий LINQ, три составляющих основной операции запроса, отложенное и немедленное выполнение, параллельные запросы.

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

В среде со сборкой мусора термин «утечка памяти» представляется немного контринтуитивным. Как может произойти утечка памяти, когда есть сборщик мусора (GC — garbage collector), который берет на себя задачу высвобождения памяти?

На это есть две основные связанные между собой причины. Первая основная причина — это когда у вас есть объекты, на которые все еще ссылаются, но которые фактически не используются. Поскольку на них ссылаются, сборщик мусора не будет их удалять, и они останутся нетронутыми навсегда, занимая память. Это может произойти, например, когда вы подписываетесь на event и никогда не отменяете подписку.

Давайте же перейдем к моему списку лучших практик:

1. Обнаружение утечек памяти с помощью окна средств диагностики

Если вы перейдете в Debug | Windows | Show Diagnostic Tools, вы увидите это окно. Как и я когда-то, вы, вероятно, уже видели это окно после установки Visual Studio, сразу же закрыли его и никогда больше о нем не вспоминали. Окно средств диагностики может быть весьма полезным. Оно может помочь вам легко обнаружить 2 проблемы: утечки памяти и GC Pressure (давление на сборщик мусора).

Когда у вас есть утечки памяти, график использования памяти процессом (Process Memory) выглядит следующим образом:

По желтым линиям, идущим сверху, вы можете наблюдать, как сборщик мусора пытается высвободить память, но загруженность памяти все-равно продолжает расти.

В случае GC Pressure, график использования памяти процессом выглядит следующим образом:

GC Pressure — это когда вы создаете и удаляете новые объекты настолько быстро, что сборщик мусора просто не успевает за вами. Как вы видите на картинке, объем потребляемой памяти близок к своему пределу, а сборка мусора происходит очень часто.

С помощью этого метода вы не сможете найти определенные утечки памяти, но вы навскидку можете обнаружить, что у вас есть проблема с утечкой памяти, что само по себе уже несет пользу. В Visual Studio Enterprise окно средств диагностики также включает встроенный профилировщик памяти, который позволяет обнаружить конкретную утечку. Мы поговорим о профилировании памяти в третьем пункте.

2. Обнаружение утечек памяти с помощью диспетчера задач, Process Explorer или PerfMon

Второй самый простой способ обнаружить серьезные проблемы с утечками памяти — с помощью диспетчера задач (Task Manager) или Process Explorer (от SysInternals). Эти инструменты могут показать объем памяти, который использует ваш процесс. Если она постоянно увеличивается со временем, возможно, у вас утечка памяти.

PerfMon немного сложнее в использовании, но у него есть хороший график потребления памяти с течением времени. Вот график моего приложения, которое бесконечно выделяет память, не освобождая ее. Я использую счетчик Process | Private Bytes.

Обратите внимание, что этот метод заведомо ненадежен. Вы можете наблюдать увеличение потребления памяти только потому, что еще не отработал сборщик мусора. Также стоит вопрос об общей и приватной памяти, поэтому вы можете упустить утечки памяти и/или диагностировать утечки, которые не являются вашими собственными (объяснение). Наконец, вы можете принять утечку памяти за GC Pressure. В этом случае у вас нет утечек памяти, но вы создаете и удаляете объекты так быстро, что сборщик мусора не поспевает за вами.

Несмотря на недостатки, я упоминаю эту технику, потому что она проста в использовании и иногда является вашим единственным подручным инструментом. Это также хороший индикатор того, что что-то не так (при наблюдении в течение очень длительного периода времени).

3. Использование профилировщика памяти для обнаружения утечек

Профилировщик в работе с утечками памяти подобен ножу шеф-повара. Это основной инструмент для их поиска и исправления. Хотя другие методы могут быть проще в использовании или дешевле (лицензии на профилировщики стоят дорого), все таки стоит овладеть навыком работы хотя с бы один профилировщиком памяти, чтобы при необходимости эффективно решать проблемы утечек памяти.

Все профилировщики работают приблизительно одинаково. Вы можете подключиться к запущенному процессу или открыть файл дампа. Профилировщик создаст снапшот текущей памяти из кучи вашего процесса. Вы можете анализировать снапшот различными способами. Например, вот список всех аллоцированных объектов в текущем снапшоте:

Вы можете увидеть, сколько аллоцировано экземпляров каждого типа, сколько памяти они занимают и путь ссылки на GC Root.

Самый быстрый и полезный метод профилирования — это сравнение двух снапшотов, в которых память должна вернуться в одно и то же состояние. Первый снимок делается перед операцией, а второй после выполнения операции. Например, вы можете повторить эти шаги:

Начните с какого-либо состояния бездействия (Idle state) в вашем приложении. Это может быть Главное меню или что-то в этом роде.

Сделайте снапшот с помощью профилировщика памяти, присоединившись к процессу или сохранив дамп.

Запустите операцию, про которую вы подозреваете, что при ней возникла утечка памяти. Вернитесь в состояние бездействия по ее окончании.

Сделайте второй снапшот.

Сравните оба снапшота с помощью своего профилировщика.

Изучите New-Created-Instances, возможно, это утечки памяти. Изучите «path to GC Root» и попытайтесь понять, почему эти объекты не были освобождены.

Вот отличное видео, где в профилировщике памяти SciTech сравниваются два снапшота, в результате чего обнаруживается утечка памяти:

4. Используйте «Make Object ID» для поиска утечек памяти

Предположим, вы подозреваете, что в определенном классе есть утечка памяти. Другими словами, вы подозреваете, что после выполнения определенного сценария этот класс остается ссылочным и никогда не собирается сборщиком мусора. Чтобы узнать, действительно ли сборщик мусора собрал его, выполните следующие действия:

Поместите точку останова туда, где создается экземпляр класса.

Наведите курсор на переменную, чтобы открыть всплывающую подсказку отладчика, затем щелкните правой кнопкой мыши и используйте Make Object ID . Вы можете ввести в окне Immediate $1 , чтобы убедиться, что Object ID был создан правильно.

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

Спровоцируйте сборку мусора с помощью известных волшебных строчек кода.

5. В появившемся окне непосредственной отладки введите $1 . Если оно возвращает null , значит, сборщик мусора собрал ваш объект. Если нет, у вас утечка памяти.

Здесь я отлаживаю сценарий с утечкой памяти:

А здесь я отлаживаю аналогичный сценарий, в котором нет утечек памяти:

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

5. Избегайте известных способов заиметь утечки памяти

Риск нарваться на утечки памяти есть всегда, но есть определенные паттерны, которые помогут получить их с большей вероятностью. Я предлагаю быть особенно осторожным при их использовании и проактивно проверять утечки памяти с помощью таких методов, как последний приведенный здесь пункт.

Вот некоторые из наиболее распространенных подозреваемых:

Статические переменные, коллекции и, в частности, статические события всегда должны вызывать подозрения. Помните, что все статические переменные являются GC Roots, поэтому сборщик мусора никогда не собирает их.

Кэширование — любой тип механизма кэширования может легко вызвать утечку памяти. Кэширую информацию в памяти, в конечном итоге он переполнится и вызовет исключение OutOfMemory. Решением может быть периодическое удаление старых элементов или ограничение объема кэширования.

Привязки WPF могут быть опасными. Практическое правило — всегда выполнять привязку к DependencyObject или к INotifyPropertyChanged. Если вы этого не сделаете, WPF создаст сильную ссылку на ваш источник привязки (то есть ViewModel) из статической переменной, что приведет к утечке памяти. Дополнительную информацию о WPF утечках можно найти в этом полезном треде StackOverflow.

Захваченные члены. Может быть достаточно очевидно, что метод обработчика событий подразумевает, что на объект ссылаются, но когда переменная захвачена анонимным методом — на нее также ссылаются. Вот пример такой утечки памяти:

Потоки, которые никогда не завершаются. Live Stack каждого из ваших потоков считается GC Root. Это означает, что до тех пор, пока поток не завершится, любые ссылки из его переменных в стеке не будут собираться сборщиком мусора. Это также включает таймеры. Если обработчик тиков вашего таймера является методом, то объект метода считается ссылочным и не собирается. Вот пример такой утечки памяти:

6. Используйте шаблон Dispose для предотвращения утечек неуправляемой памяти

Оператор using за кулисами преобразует код в оператор try / finally , где метод Dispose вызывается в finally .

Когда вы сами выделяете неуправляемые ресурсы, вам определенно следует использовать шаблон Dispose . Вот пример:

Смысл этого шаблона — разрешить явное удаление ресурсов. А также чтобы добавить гарантии того, что ваши ресурсы будут удалены во время сборки мусора (в Finalizer ), если Dispose() не был вызван.

GC.SuppressFinalize(this) также имеет важное значение. Она гарантирует, что Finalizer не будет вызван при сборке мусора, если объект уже был удален. Объекты с Finalizer-ами освобождаются иначе и намного дороже. Finalizer добавляется к F-Reachable-Queue , которая позволяет объекту пережить дополнительную генерацию сборщика мусора. Есть и другие сложности.

7. Добавление телеметрии памяти из кода

Иногда вам может понадобиться периодически регистрировать использование памяти. Возможно, вы подозреваете, что на вашем рабочем сервере есть утечка памяти. Возможно, вы захотите предпринять какие-то действия, когда ваша память достигнет определенного предела. Или, может быть, у вас просто есть хорошая привычка следить за своей памятью.

Из самого приложения мы можем получить много информации. Получить текущую используемую память очень просто:

Для получения дополнительной информации вы можете использовать PerformanceCounter — класс, который используется для PerfMon :

Заключение

Не знаю, как у вас, но моя цель, поставленная на новый год, такова: лучшее управление памятью.

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


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

Когда это происходит, приложение постепенно замедляется и в какой-то момент работать с ним становится невозможно. В некоторых случаях это может замедлить работу веб-браузера и компьютера. Такие проблемы трудно отслеживать и отлаживать, поскольку они сразу не видны и проявляются только при длительной работе.

Для отслеживания этих неприятностей можно использовать Sematext Experience – инструмент мониторинга, позволяющий не только контролировать использование веб-приложениями памяти, но также быстро и эффективно обнаруживать утечки. В статье мы подробно рассмотрим, как это делается.


Что такое утечка памяти

Утечка памяти – это избыточное потребление ресурсов из-за неправильного управления распределением памяти в программном обеспечении. Она происходит, когда веб-приложение выделяет память и продолжает использовать ее даже когда та больше не нужна.

Что вызывает утечку памяти: признаки, на которые следует обратить внимание

Веб-приложение будет создавать и использовать различные константы, переменные и функции для выполнения работы. Все они требуют памяти. В JavaScript и других языках программирования высокого уровня, когда ресурс больше не нужен, он будет удален автоматической системой управления памятью, называемой сборщиком мусора.

Память освобождается, когда она больше не выделена в коде. Однако бывают ситуации, когда память продолжает быть выделенной на протяжении всего жизненного цикла приложения. Иногда это необходимо, но если мы больше не нуждаемся в информации, а область памяти все еще выделена, можно говорить об утечке.

Во фронтенд-приложениях существует несколько распространенных паттернов, которые могут вызвать утечку памяти. К ним относятся:

  • Замыкания – внутренние функции, имеющие ссылки на переменные внешних. Таким образом внутренняя функция не позволяет сборщику мусора освободить память.
  • Случайные глобальные переменные – ранее необъявленные переменные станут глобальными и не будут освобождены.
  • Несвязанные коллекции – массивы, мапы и наборы позволяют хранить данные в каком-то виде, но с багами в коде они могут привести к утечке памяти из-за вечного хранения ссылок. Такой пример будет рассмотрен ниже.
  • Отсоединенный DOM – элемент Document Object Model, который больше не используется, но на него продолжают ссылаться.
  • Несвязанные таймеры – работающие вечно и сохраняющие объекты таймеры могут привести к утечке памяти.

Как обнаружить утечки памяти: пример приложения

Дальнейшая идея заключается в том, чтобы хранить все в бекенде, периодически отправляя данные. Однако чтобы создать утечку, мы забываем сделать одну штуку: после отправки данных в бекенд не очищаем массив. Это означает, что на данные по-прежнему ссылаются из нашего веб-приложения. Если пользователь не закрывает вкладку, данные будут продолжать добавляться, что приведет к утечке памяти.

Теперь посмотрим, как можно идентифицировать возникновение утечки памяти с помощью Sematext и его способности измерять использование памяти сайтом и приложением.

Интеграция Sematext Experience с веб-приложением

Первый шаг – настроить приложение на использование Sematext Browser SDK для отправки данных в облако Sematext.

Для этого добавим скрипт перед закрывающим тегом </head>:

Второй шаг зависит от типа вашего веб-приложения и используемого фреймворка – нужно сообщить Browser SDK, как ему себя настроить. Например, для стандартного развертывания нескольких веб-страниц вы бы использовали перед закрывающим тегом </head> что-то вроде этого:

Если вы используете какую-либо одностраничную архитектуру, инструкции по установке приложения Sematext Experience помогут выполнить все необходимые для отправки метрик шаги.

Выявление утечки памяти

Зная что делает пользователь, мы можем взглянуть на графики в Sematext Experience. Перейдите к отчету об использовании памяти в приложении:


Если бы у вас было подобное смоделированному веб-приложение с утечкой , вы могли бы наблюдать, как бесконечно растет потребление памяти. Не совсем бесконечно: рано или поздно работа браузера завершится аварийно . Диаграмма использования памяти должна быть привязана к некоторым значениям. Например, вот так:



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

Поддерживаемые браузеры

Возможности измерения памяти Sematext Browser SDK зависят от используемого браузером API для предоставления связанных с памятью метрик. Поддерживается он только в Chrome и проходил испытания Chrome Origin для Chrome 82-87. Начиная с Chrome 89 API будет полностью доступен — это означает, что пользователям потребуется новейший веб-браузер Google.

Заключение

С помощью правильных инструментов выявить проблемы с утечкой памяти довольно просто. Именно для этой цели был создан и постоянно совершенствуется Sematext Experience – он предоставляет вам комплексное, доступное и простое в использовании решение для мониторинга.

Утечки памяти в Windows 10


Почему это плохо

  1. Поскольку память не освобождается, даже когда она не используется, это приводит к ее истощению.
  2. Исчерпание памяти приводит к старению программного обеспечения.
  3. Уменьшение доступной памяти приводит к увеличению времени отклика и снижению производительности системы.
  4. Неконтролируемая утечка памяти может в конечном итоге привести к сбою приложения.

Чтобы идентифицировать утечку памяти, программист должен иметь доступ к исходному коду программы.

Обнаружение утечки

Чтобы решить проблему, нам нужно сначала ее идентифицировать. Основные шаги по обнаружению утечки памяти:

  1. Подтверждение . Определение наличия утечки.
  2. Поиск утечки памяти в режиме ядра . Поиск утечки, вызванной компонентом драйвера режима ядра.
  3. Поиск утечки памяти в пользовательском режиме . Поиск утечки, вызванной драйвером пользовательского режима или приложением.

Распределение памяти

Существуют разные режимы, в которых приложения выделяют оперативную память. Если пространство не освобождается после использования, утечка памяти будет происходить независимо от режима выделения. Некоторые общие шаблоны распределения:

  1. Функция HealAlloc для выделения кучи памяти. Эквивалентами времени выполнения C/C ++ являются malloc и новые.
  2. Функция VirtualAlloc для прямого выделения из ОС.
  3. Kernel32 API для хранения памяти ядра для приложения. Пример, CreateFile, CreateThread.
  4. User32 API и Gdi32 API.

Предотвращение утечек памяти

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

Мониторинг привычек


Вы должны следить за ненормальным использованием ОЗУ отдельными программами и приложениями. Вы можете перейти в диспетчер задач Windows, нажав CTRL + SHIFT + ESC и добавить такие столбцы, как дескрипторы, объекты пользователя, объекты GDI и т. Д.

Это поможет вам легко отслеживать использование ресурсов.

Инструменты Microsoft для диагностики утечек памяти

Различные инструменты диагностируют утечки памяти для различных режимов выделения:

  1. Верификатор приложения диагностирует утечки кучи.
  2. UMDH (компонент средств отладки Windows) диагностирует утечки для отдельных процессов, отслеживая выделение кучи памяти.
  3. Trace Capture для тщательного анализа использования оперативной памяти.
  4. Xperf также отслеживает шаблоны распределения кучи.
  5. CRT Debug Heap не только отслеживает выделение кучи, но также позволяет использовать методы кодирования для минимизации утечек.
  6. JavaScript Memory Leak Detector отлаживает утечки памяти в кодах.

Советы по использованию

Устранение утечек памяти в Windows

Так же, как различные способы предотвращения утечек памяти, существуют различные способы остановить утечки памяти.

1] Закройте процессы и перезапустите.

Если вы видите, что ненужный процесс занимает слишком много ОЗУ, вы можете завершить процесс в диспетчере задач. Вам нужно будет перезагрузить устройство, чтобы освободившееся пространство было доступно для использования другими процессами. Без перезагрузки проблема утечки памяти не будет решена. Одним из конкретных процессов, которые имеют ошибки для замедления работы ПК, является Runtime Broker. Попробуйте, если отключение, которое само по себе работает.

2] Инструменты диагностики памяти


Чтобы получить доступ к встроенному инструменту диагностики памяти для Windows:

  1. Сохраните всю вашу важную работу.
  2. Нажмите Win + R , чтобы открыть окно Выполнить .
  3. Введите команду mdsched.exe в окне Выполнить .
  4. Перезагрузите компьютер.
  5. После перезапуска выполните базовое сканирование или выберите параметры Расширенные , например Test mix ’или Количество проходов ’.
  6. Нажмите F10 , чтобы начать тестирование.

Это все еще временные исправления.

3] Проверить обновления драйверов


Устаревшие драйверы вызывают утечки памяти. Держите все драйверы обновленными:

  1. Нажмите Win + R и откройте окно Выполнить . Введите devmgmt.msc и нажмите Enter. Вы попадете в Диспетчер устройств .
  2. Проверьте устаревшие драйверы и обновите их все.
  3. Для обновлений, которые вы могли пропустить, проверьте в Центре обновления Windows.

Это было просто.

4] Оптимизация производительности


Настройка Windows на производительность будет управлять всем, включая планирование процессора и использование памяти, чтобы предотвратить утечки памяти. Следуй этим шагам:

Если это простое решение не сработало, попробуйте следующее решение.

5] Отключить программы, запускаемые при запуске


6] Дефрагментация жесткого диска


Хотя Windows 10 делает это для вас автоматически, вам может понадобиться время от времени выполнять дефрагментацию жестких дисков для оптимизации производительности:

Перезагрузите компьютер после новой фрагментации.

7] Файл ClearPage при завершении работы

Сейчас становится все сложнее, но не волнуйтесь. Вот как очищать файл подкачки при каждом выключении:

  1. Введите regedit в поле поиска, чтобы запустить редактор реестра.
  2. Введите этот путь: HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlSession ManagerMemory Management
  3. Измените значение ClearPageFileAtShutDown на «1».
  4. Сохраните изменения и перезагрузите устройство.

Это должно сделать это.

9] Отключить суперпатч

Этот сервис Windows оптимизирует производительность за счет минимизации времени загрузки. Это позволяет Windows управлять использованием оперативной памяти. Жизнь после отключения Superfetch не удобна, но сделайте это, если нужно. По крайней мере, попробуйте это в одиночку, чтобы изолировать проблему:

  1. Найдите services.msc и перейдите в диспетчер служб.
  2. Найдите Superfetch и нажмите его правой кнопкой мыши, чтобы перейти в Свойства .
  3. Выберите « Стоп ».
  4. Также Отключить ’сервис из раскрывающегося меню.
  5. Перезагрузите компьютер и проверьте, не улучшилась ли производительность.

Включите Superfetch, если этот не работает.

10] Проверка на наличие вредоносных программ

Используйте стороннее антивирусное программное обеспечение или встроенный в Windows 10 Защитник Windows для сканирования и устранения вредоносных программ.Убедитесь, что антивирус обновлен для поддержки вашей ОС, чтобы он не стал причиной утечки памяти.

Каждое решение, которое вам когда-либо понадобится, чтобы найти или предотвратить утечку памяти, находится здесь. Вы можете прочитать больше об утечках памяти на MSDN и Microsoft.

В статье мы расскажем, что такое утечка памяти, как происходит и какие вызывает последствия для операционной системы Android. Также рассмотрим инструменты для обнаружения утечек памяти, типовые модели утечки памяти в Android, способы оценки степени критичности и методы предотвращения основных видов утечек.

Каждому приложению для нормальной работы нужна оперативная память. Для обеспечения необходимым количеством памяти всех приложений Android должен эффективно управлять выделением памяти под каждый процесс. Среда выполнения Android запускает сборку мусора (GC), когда оперативная память заканчивается.

Что такое сборщик мусора?

Java Memory Management со встроенным сборщиком мусора является одним из лучших достижений этого языка. Он позволяет разработчикам создавать новые объекты, не заботясь о распределении памяти и ее освобождении, поскольку сборщик мусора автоматически восстанавливает память для повторного ее использования. Это обеспечивает более быструю разработку с меньшим количеством кода, одновременно устраняя утечки памяти и другие проблемы, связанные с ней. По крайней мере, в теории.

По иронии судьбы сборщик мусора Java работает слишком хорошо, создавая и удаляя большое количество объектов. Большинство проблем управления памятью решаются, но часто за счет уменьшения производительности. Создание универсального сборщика мусора, применяемого ко всем возможным ситуациям, привело к сложностям с оптимизацией системы. Чтобы разобраться со сборщиком мусора, нужно сначала понять, как работает управление памятью на виртуальной машине Java (JVM).

Как работает сборщик мусора

Многие считают, что сборщик мусора собирает и удаляет из памяти неиспользуемые объекты. На самом деле сборщик мусора Java делает все наоборот. Живые объекты отмечаются как активные, а все остальное считается мусором. Как следствие, эта фундаментальная особенность может привести ко многим проблемам с производительностью.

Начнем с так называемой кучи (англ. «heap») — области памяти, используемой для динамического распределения ресурсов приложений. В большинстве конфигураций операционная система заранее отдает эту часть под управление JVM во время работы программы. Это приводит к последствиям:

  • создание объекта происходит быстрее, потому что глобальная синхронизация с операционной системой не требуется для каждого отдельного объекта. В процессе выделения памяти под приложение JVM просто фиксирует за задачей определенный участок памяти и перемещает указатель смещения вперед (картинка ниже). Следующее распределение начинается с этого смещения и занимает следующий участок памяти;
  • когда объект больше не используется, сборщик мусора восстанавливает базовое состояние этого участка памяти и повторно использует ее для размещения другого объекта. Это означает, что нет явного удаления и память все еще не будет очищена.


Новые объекты просто размещаются в конце кучи.

Все объекты размещены в куче, управляемой JVM. Каждый элемент, используемый разработчиком, обрабатывается таким образом, включая объекты класса, статические переменные и даже сам код. Пока объект ссылается на что-то, JVM считает его используемым. Когда объект больше не ссылается и, следовательно, недоступен по коду приложения, сборщик мусора удаляет его и восстанавливает неиспользуемую память. Все настолько просто, как и звучит, но возникает вопрос: какова первая ссылка в дереве объектов?

Корни сборщика мусора — начальная позиция всех иерархий (деревьев) объектов

Каждое дерево объектов должно иметь один или несколько корневых объектов. Пока приложение может достичь этих корней, все дерево доступно. Но когда эти корневые объекты считаются доступными? Специальные объекты, называемые корнями сборщика мусора (корни GC, рисунок ниже), всегда доступны, а также любой объект, чьим корнем является корень сборщика мусора.

4–5 декабря, Онлайн, Беcплатно

В Java существуют следующие типы корней сборщика мусора:

  • локальные переменные поддерживаются активными благодаря стеку потока. Это фиктивная виртуальная ссылка на объект и, следовательно, она не видна. Для всех целей и задач локальные переменные являются корнями сборщика мусора;
  • активные потоки Java всегда считаются используемыми объектами и поэтому являются корнями сборщика мусора. Это особенно важно для локальных переменных потока;
  • на статические переменные ссылаются их классы. Это делает их де-факто корнями сборщика мусора. Сами классы могут быть собраны сборщиком, что приведет к удалению всех статических переменных, на которые они ссылаются. Это имеет особо важно, когда мы используем серверы приложений, контейнеры OSGi или загрузчики классов в целом.


Корни сборщика мусора — это объекты, которые ссылаются на JVM и, таким образом, остаются в памяти устройства.

Поэтому простое Java-приложение имеет следующие корни сборщика мусора:

  • локальные переменные в главном методе;
  • основной поток;
  • статические переменные главного класса.

Маркировка и сборка мусора

Чтобы определить, какие объекты больше не используются, JVM периодически запускает алгоритм маркировки и сборки мусора:

  1. Алгоритм «проходит» по всей иерархии объектов, начиная с корней сборщика мусора, и отмечает каждый найденный объект как активный.
  2. Вся участки памяти, не содержащие активных объектов (а точнее объектов, которые не были отмечены в предыдущем шаге), восстанавливаются. Они просто обозначаются как свободные.

Сборщик мусора предназначен для устранения причины утечки памяти — недостижимых, но не удаленных объектов в памяти. Однако это работает только для утечек памяти в классическом их понимании. Возможно, что неиспользуемые объекты по-прежнему доступны приложению, потому что разработчик просто забыл очистить ссылки на них. Такие объекты не могут быть собраны сборщиком. Хуже того, такая логическая утечка памяти не может быть обнаружена никаким программным обеспечением.


Простыми словами, в памяти остаются только те объекты, которые используются пользователем.

Однако, когда код написан плохо, неиспользуемые объекты могут ссылаться на несуществующие объекты, и сборщик мусора отмечает их как активные и не может их удалить. Это и называется утечкой памяти.

Почему утечка памяти — это плохо?

Ни один объект не должен оставаться в памяти дольше, чем нужно. Ведь эти ресурсы могут пригодиться для задач, которые могут иметь реальную ценность для пользователя. В частности, для Android это вызывает следующие проблемы:

Во-первых, когда происходят утечки, доступной для использования памяти становится меньше, что вызывает более частые запуски сборщика мусора. Такие запуски останавливают рендеринг пользовательского интерфейса, а также вызывают остановку других компонентов, необходимых для нормальной работы системы. В таких случаях прорисовка кадра длиться дольше обычных 16 мс. Когда прорисовка опускается до отметки ниже 100 мс, пользователи начнут замечать замедления в работе приложений.




В Android отзывчивость приложений контролируется менеджером активности и менеджером окон. Система откроет диалог ANR (приложение не отвечает) для конкретного приложения, когда будет выполнено одно из следующих условий:

  • приложение не отвечает на нажатие клавиш, или нажатия на экран на протяжении 5 секунд;
  • BroadcastReceiver не завершился на протяжении 10 секунд;


Во-вторых, приложение с утечкой памяти не сможет получить дополнительные ресурсы от неиспользуемых объектов. Оно сделает запрос на выделение дополнительной памяти, но всему есть свой предел. Android откажется выделять больше памяти для таких приложений. Когда это произойдет, приложение просто упадет. Это может вызвать негативные эмоции у пользователей, а они, в свою очередь, могут не только удалить приложение, но и оставить негативные отзывы о нем в магазине приложений.

Как определить утечку?

Чтобы определить утечку памяти, необходимо очень хорошо разбираться в работе сборщика мусора. Но Android также может предоставить несколько хороших инструментов, которые могут помочь определить возможные утечки или найти подозрительный кусок кода.


Приложение Leak Canary от Square — хороший инструмент для обнаружения утечек памяти в приложении. Оно создает ссылки на объекты вашего приложения и проверяет, удаляются ли эти ссылки сборщиком мусора. Если нет, тогда все данные записываются в файл .hprof и проводится анализ на наличие утечек памяти. Если утечка все же будет обнаружена, приложение пришлет вам уведомление о том, как это происходит. Рекомендуется использовать это приложение до выпуска в продакшн.Android Studio также имеет удобный инструмент для обнаружения утечек памяти. Если есть подозрения, что часть кода в вашем приложении может вызывать утечку, тогда можно сделать следующее:

  1. Скомпилировать и запустить отладочную версию сборки на эмуляторе или устройстве подключенному к вашему компьютеру;
  2. Перейти к подозрительной операции, затем вернуться к предыдущему действию, которое выведет подозрительную операцию из стека задач;
  3. В Android Studio открыть Android Monitor window → Memory section и нажать на кнопку запуска сборщика мусора (Initiate GC). Затем нажать кнопку Dump Java Heap ;
  4. После нажатия кнопки Dump Java Heap Android Studio откроет файл .hprof . Существует несколько способов проверки утечки памяти через этот файл. Вы можете использовать Analyzer Tasks в правом верхнем углу для автоматического обнаружения утечек. Или же можно переключиться в режим Tree View и найти действие, которое должно быть отключено. Проверяем данные Total Count , и если нашли отличия в данных, значит, что где-то есть утечка памяти.
  5. Как только была обнаружена утечка, нужно проверить дерево ссылок и узнать, какой объект ее вызывает.

Каковы общие схемы утечек?

Есть множество причин, по которым происходит утечка памяти в Android. Но все они могут быть отнесены к трем категориям.

  • утечки памяти, инициируемые статической ссылкой;
  • утечки памяти, инициируемые рабочим процессом;
  • просто утечка.

Можно загрузить приложение SinsOfMemoryLeaks, которое поможет определить, где происходит утечка.


А теперь быстро пройдемся по всем видам утечек.

Утечки памяти, инициируемые статической ссылкой

Статическая ссылка сохраняется до тех пор, пока ваше приложение находится в памяти. У операций есть свои жизненные циклы, которые прекращаются и начинаются во время работы с приложением. Если вы обращаетесь к операции прямо или косвенно со статической ссылки, сборщик мусора не очистит занимаемую память после завершения операции. Память, занимаемая определенной операцией, может варьировать от нескольких килобайт до нескольких мегабайт в зависимости от того, в каком состоянии находится приложение. Если у него большая иерархия представлений или изображения с высоким разрешением, это может привести к утечке большого количества памяти.

Некоторые особенности утечек для этой категории:

Утечки памяти, инициируемые рабочим процессом

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

Тот же принцип применяется к таким потокам, как thread pool или ExecutorService .

Просто утечка

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

Каково влияние конкретной утечки?

Насколько велика утечка памяти?

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

Как долго длится утечка?

Некоторые утечки через рабочий поток живут до тех пор, пока работает этот поток. В таком случае нужно изучить насколько долго живет этот поток. В примере приложения выше созданы бесконечные циклы в рабочем потоке, поэтому они постоянно держат в памяти объект, порождающий утечку. Но на самом деле большинство рабочих потоков выполняет простые задачи, такие как доступ к файловой системе или выполнение сетевых вызовов, которые либо недолговечны, либо ограничены тайм-аутом.

Сколько объектов в утечке?

В некоторых случаях утечку порождает только один объект, например, один из примеров статических ссылок, показанный в приложении SinsOfMemoryLeaks. Как только будет создано новое действие, оно начнет ссылаться на новую операцию. Старая утечка будет очищена сборщиком мусора. Таким образом, максимальная утечка всегда равна размеру одного экземпляра операции. Однако другие утечки продолжают просачиваться в новые объекты по мере их создания. В примере Leaking Threads активность пропускает по одному потоку каждый раз при его создании. Поэтому, если вы поворачиваете устройство 20 раз, утечка составит 20 рабочих потоков. Это закончится весьма печально, так как приложение заполнит всю доступную память на устройстве.

Посмотрите как происходит устранение типичных утечек памяти в этой ветке репозитория. Решения можно обобщить до следующих пунктов:

  1. Нужно быть очень осторожными, принимая решение установки статической переменной для рабочего процесса. Это действительно необходимо? Возможно, эта переменная ссылается на процесс напрямую или косвенно (ссылка на объект внутреннего класса, прикрепленный экран и т. д.)? Если да, возможно ли будет очистить отсылку к процессу, используя функцию onDestroy ?
  2. Если было решено передавать операцию как синглтон или x-manager , нужно понимать, что делает другой объект с экземпляром действия. Нужно очистить ссылку (установить в null), если необходимо, используя для этого процесса функцию onDestroy .
  3. При создании класса внутри процесса, по возможности старайтесь сделать его статическим. Внутренние классы и анонимные классы имеют неявную ссылку на родительский класс. Поэтому, если экземпляр внутреннего/анонимного класса живет дольше, чем родительский класс, могут возникнуть проблемы. Например, при создании анонимного класса runnable и передаче его в рабочий поток или класс анонимного обработчика и использования его для передачи задач в другой поток существует риск утечки содержащегося объекта класса. Чтобы избежать риска утечки, нужно использовать статический класс, а не внутренний/анонимный класс.
  4. Если писать синглтон или x-manager класс, нужно сохранить ссылку на экземпляр слушателя (англ. «listener»). При этом вы не контролируете, что происходит со ссылкой (удалил ее пользователь класса или нет). В этом случае можно использовать WeakReference для создания ссылки на экземпляр слушателя. WeakReference не мешает сборщику мусора производить свои действия. Хотя эта функция отлично подходит для предотвращения утечек памяти, она также может вызвать побочный эффект, потому что нет гарантии, что ссылочный объект является активным, когда это необходимо. Поэтому рекомендуется использовать его в качестве последнего средства для исправления утечек памяти.
  5. Всегда нужно завершать рабочие потоки, инициированные функцией onDestroy() .

Не забудьте проверить примеры кода для типичных утечек памяти и способы их избежания в репозитории на Github.

Читайте также: