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

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

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

Монитор производительности в Chrome DevTools

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

  • использование процессора
  • Размер кучи JS
  • Узлы DOM
  • Слушатели событий JS
  • Документы
  • Рамки для документов
  • Макеты / сек
  • Пересчет стилей / сек

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

Использование ЦП

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

Размер кучи JS

Кучи в JavaScript - это долгоживущие объекты, разница между созданными и удаленными объектами. Кучи появляются при утечке памяти. Утечка памяти - это когда объект в программе все еще потребляет выделенную ему память после того, как код был прочитан и объект был оценен. Общее значение - снижение производительности приложения.

Узлы DOM

DOM, сокращение от Direct Object Model, представляет собой древовидную структуру, представляющую HTML-код веб-сайта, и каждый элемент HTML называется узлом.

HTML-элемент обычно состоит из начального и конечного тегов. В приведенном ниже примере у нас есть всего три HTML-элемента: div, p и span.

<div>
    <p>Hello World!</p>
    <span>This is a span</span>
<div>

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

Слушатели событий JS

Интерфейс EventListener представляет собой объект, который может обрабатывать событие, отправленное объектом EventTarget. Эта концепция чрезвычайно важна в JavaScript.

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

Обзор приложения

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

Каждый дочерний компонент представляет одну единственную безопасность. Каждый дочерний компонент будет связываться с конечной точкой Yahoo Finance (API), чтобы получить текущую цену для этой конкретной акции через заданный интервал.

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

При инициализации дочернего компонента я использую функцию таймера, которая создает Observable, который начинает излучать после времени dueTime и выдает все увеличивающиеся числа после каждого периода времени после этого. Другими словами, эта функция запускается через 500 мс для выполнения вызова службы getStockInformation, который связывается с конечной точкой Yahoo Finance для получения данных.

Ниже приведен снимок экрана с кодом внутри ловушки жизненного цикла ngOnInit. Обратите внимание на следующий код:

.subscribe(result => { … });

Мы говорим коду подписаться на метод службы данных getStockInformation.

Запустите приложение в браузере Chrome и откройте DevTools, найдите вкладку «Сеть» и щелкните по ней. Вы должны увидеть, что каждые 500 мс к конечной точке Yahoo Finance отправляется запрос GET для получения информации о запасах, определенных в запросе.

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

На странице вы заметите кнопку Скрыть / показать дочерние компоненты. Эта кнопка предназначена для установки значения * ngIf для каждого дочернего компонента в основном компоненте в значение false. Это в основном удаляет все дочерние компоненты из DOM.

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

В качестве примечания, разница между ngIf и ngShow заключается в том, что ngIf фактически удаляет элементы HTML из DOM, в то время как ngShow скрывает их от пользователя, но элементы HTML останутся в DOM.

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

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

Итак, теперь, когда мы открываем инструменты разработчика Chrome ›Элементы для анализа DOM, мы должны заметить, что ранее отрисованные дочерние компоненты больше не присутствуют.

Кроме того, если вы нажмете на вкладку «Сеть», вы действительно должны увидеть, что в настоящее время в Yahoo Finance не поступают запросы GET. Однако, как ни странно, запросы все еще выполняются.

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

У каждой системы безопасности теперь будет два запроса к конечной точке Yahoo Finance каждые 500 мс. И снова, если вы нажмете кнопку, чтобы скрыть, а затем отобразить дочерние компоненты, количество запросов GET для каждой безопасности увеличивается на единицу и теперь составляет три.

К настоящему времени вы, вероятно, поняли, в чем проблема. Каждый раз, когда инициализируется дочерний компонент, создается новая подписка на вызов службы getStockInformation с таймером на 500 мс, который работает до тех пор, пока код не откажется от подписки на службу.

В моем предыдущем примере я трижды скрыл и показал дочерние компоненты, теперь код имеет три активных подписки для каждой отдельной ценной бумаги.
Учитывая, что у нас есть 18 ценных бумаг и три активных подписки для каждой ценной бумаги, код теперь составляет 18 * 3 = 54 запроса GET каждые 500 мс к службе Yahoo Finance.

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

Если вы позволите приложению работать в течение определенного времени, вы заметите, что приложение начнет работать медленно и очень медленно.

Как отладить проблему с утечкой памяти

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

После загрузки монитора производительности в DevTools я начинаю смотреть на использование ЦП. В этом примере первое, что мне бросилось в глаза, - это процент использования ЦП, потребляемого приложением, по отношению к объему выполняемой им работы (в обычном сценарии процент ЦП для этого приложения не должен превышать ~ 21%).

Затем вы заметите, что размер кучи JS составляет 25,6 МБ.

Узлы DOM также очень важны, потому что они говорят нам, сколько узлов в настоящее время присутствует в памяти. В нашем случае это число велико по сравнению с тем, где оно должно быть (~ 303) для этого приложения.

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

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

Без отказа от подписки на службу код будет продолжать выполняться в фоновом режиме, даже если дочерний компонент будет уничтожен. И каждый раз при инициализации дочернего компонента будет создана новая подписка. Без надлежащего отказа от подписки на уничтожение компонента у нас не будет ссылки на него для отказа от подписки в будущем, и единственный способ сбросить / исправить память - это перезагрузить приложение в браузере.

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

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

Эта простая очистка в приложении значительно улучшает код. Ниже приведен снимок экрана с показателями монитора производительности для приложения с установленной очисткой.

Можно легко добиться следующих улучшений:
Использование ЦП улучшилось на ~ 28 процентов
Узлы DOM улучшились на ~ 30 процентов
Слушатели событий JS улучшились на ~ 20 процентов

Ниже показан экран с метрической диаграммой монитора производительности.

Кроме того, когда мы посмотрим на вкладку «Сеть» в DevTools, вы заметите, что запросы GET останавливаются, когда дочерние компоненты скрыты / удалены из DOM.

Если вы посмотрите на снимок экрана ниже, вы заметите статус для этого самого последнего запроса в списке как (отменен). Это показатель того, что когда мы скрывали запросы дочерних компонентов, они фактически остановились, и если запросы находились в процессе выполнения, они фактически отменяются. Это именно то, что мы хотим видеть.

Вывод

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

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