Как устранить проблемы с памятью

Kayce Basques
Kayce Basques

Узнайте, как использовать Chrome и DevTools для поиска проблем с памятью, влияющих на производительность страницы, включая утечки памяти, раздувание памяти и частые сборки мусора.

Сводка

  • Узнайте, сколько памяти использует страница, с помощью диспетчера задач Chrome.
  • Визуализируйте использование памяти с течением времени с помощью записей временной шкалы.
  • Выявлять отсоединенные деревья DOM (распространенная причина утечек памяти) с помощью снимков кучи.
  • Узнайте, когда в куче JS выделяется новая память, с помощью записей временной шкалы распределения.
  • Определите отсоединенные элементы, которые сохраняются с помощью ссылки JavaScript.

Обзор

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

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

  • Эффективность страницы со временем снижается. Это может быть признаком утечки памяти. Утечка памяти – это ситуация, когда из-за ошибки на странице она со временем начинает использовать все больше и больше памяти.
  • Страница постоянно работает плохо. Это может быть признаком раздувания памяти. Раздувание памяти – это когда страница использует больше памяти, чем необходимо для оптимальной скорости загрузки.
  • Страница работает медленно или часто зависает. Возможно, это связано с частой сборкой мусора. Сборка мусора – это процесс, при котором браузер освобождает память. Браузер решает, когда это произойдет. Во время сбора данных выполнение всех скриптов приостанавливается. Если браузер часто собирает мусор, выполнение скрипта будет часто приостанавливаться.

Раздувание памяти: сколько это "слишком много"?

Утечку памяти легко определить. Если сайт постепенно использует все больше и больше памяти, значит, у вас есть утечка. Но раздувание памяти определить немного сложнее. Что значит "использует слишком много памяти"?

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

Главное — использовать модель RAIL и ориентироваться на пользователей. Узнайте, какие устройства популярны среди ваших пользователей, и протестируйте свою страницу на этих устройствах. Если страница постоянно работает плохо, возможно, она превышает возможности памяти этих устройств.

Как отслеживать использование памяти в реальном времени с помощью диспетчера задач Chrome

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

  1. Нажмите Shift + Esc или в главном меню Chrome выберите Дополнительные инструменты > Диспетчер задач.

    Открывается Диспетчер задач.

  2. Нажмите правой кнопкой мыши на заголовок таблицы в Диспетчере задач и выберите Память JavaScript.

    Включение памяти JS в заголовке диспетчера задач.

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

  • В столбце Объем потребляемой памяти указан объем памяти, используемой ОС. Узлы DOM хранятся в памяти ОС. Если это значение увеличивается, значит создаются узлы DOM.
  • Столбец Память JavaScript представляет кучу JS. Этот столбец содержит два значения. Вам нужно значение в скобках. Число в столбце Live показывает, сколько памяти используют доступные объекты на странице. Если это значение растет, значит создаются новые объекты или увеличиваются существующие.

    Диспетчер задач с включенным заголовком "Память JavaScript".

Как визуализировать утечки памяти с помощью записей производительности

Вы также можете начать анализ с панели Производительность. Панель "Производительность" позволяет визуализировать использование памяти страницей с течением времени.

  1. Откройте панель Производительность в инструментах разработчика.
  2. Установите флажок Память.
  3. Как создать запись

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

var x = [];

function grow() {
  for (var i = 0; i < 10000; i++) {
    document.body.appendChild(document.createElement('div'));
  }
  x.push(new Array(1000000).join('x'));
}

document.getElementById('grow').addEventListener('click', grow);

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

Простой пример роста.

Сначала мы расскажем о пользовательском интерфейсе. График HEAP на панели Overview (Обзор) (под графиком NET) представляет кучу JS. Под панелью Обзор находится панель Счетчик. Здесь вы можете увидеть использование памяти с разбивкой по куче JS (то же, что и график HEAP на панели Overview), документам, узлам DOM, прослушивателям и памяти графического процессора. Если снять флажок, соответствующий показатель будет скрыт.

Теперь сравним код со скриншотом. Если вы посмотрите на счетчик узлов (зеленый график), то увидите, что он точно соответствует коду. Количество узлов увеличивается дискретными шагами. Можно предположить, что каждое увеличение количества узлов – это вызов grow(). График кучи JS (синий) не такой простой. В соответствии с рекомендациями первое падение – это принудительный сбор мусора (выполняется нажатием кнопки Сбор мусора). По мере записи вы можете видеть, что размер кучи JS резко возрастает. Это естественно и ожидаемо: код JavaScript создает узлы DOM при каждом нажатии кнопки и выполняет большую работу, когда создает строку из миллиона символов. Главное, что нужно заметить, – это то, что в конце куча JS больше, чем в начале (под началом здесь понимается момент после принудительной сборки мусора). В реальном мире, если бы вы увидели эту картину увеличения размера кучи JS или размера узла, это потенциально означало бы утечку памяти.

Как обнаруживать утечки памяти в отсоединенных деревьях DOM с помощью снимков кучи

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

Вот простой пример отсоединенных узлов DOM.

var detachedTree;

function create() {
  var ul = document.createElement('ul');
  for (var i = 0; i < 10; i++) {
    var li = document.createElement('li');
    ul.appendChild(li);
  }
  detachedTree = ul;
}

document.getElementById('create').addEventListener('click', create);

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

Снимки кучи – один из способов выявить отсоединенные узлы. Как следует из названия, снимки кучи показывают, как распределяется память между объектами JS и узлами DOM на странице в момент создания снимка.

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

Выбран переключатель &quot;Сделать снимок кучи&quot;.

Обработка и загрузка снимка может занять некоторое время. Когда снимок будет готов, выберите его на панели слева (в разделе Снимки кучи).

Введите Detached в поле Фильтр классов, чтобы найти отсоединенные деревья DOM.

Фильтрация по отсоединенным узлам.

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

Проверка отсоединенного дерева.

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

Анализ отсоединенного узла.

Как выявлять утечки памяти в куче JS с помощью хронологии распределения памяти

Временная шкала распределения – ещё один инструмент, который поможет вам отслеживать утечки памяти в куче JS.

Чтобы продемонстрировать временную шкалу распределения, рассмотрим следующий код:

var x = [];

function grow() {
  x.push(new Array(1000000).join('x'));
}

document.getElementById('grow').addEventListener('click', grow);

Каждый раз, когда пользователь нажимает кнопку, указанную в коде, в массив x добавляется строка из миллиона символов.

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

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

Новые распределения на временной шкале эффективности.

Эти синие полосы представляют собой новые выделения памяти. Эти новые выделения памяти являются вашими кандидатами на утечку памяти. Вы можете увеличить масштаб полосы, чтобы отфильтровать панель Constructor (Конструктор) и показать только объекты, которые были выделены в течение указанного периода времени.

Увеличенная временная шкала распределения.

Разверните объект и нажмите на его значение, чтобы посмотреть подробные сведения на панели Объект. Например, на скриншоте ниже показаны сведения о новом объекте, из которых видно, что он был выделен переменной x в области видимости Window.

Сведения об объекте массива строк.

Как изучить распределение памяти по функциям

Чтобы посмотреть распределение памяти по функциям JavaScript, на панели Память выберите тип профиля Выборка распределения.

Профилировщик выборки распределения на панели &quot;Память&quot;.

  1. Установите переключатель в положение Выборка распределения. Если на странице есть рабочий поток, вы можете выбрать его в качестве цели профилирования в окне Select JavaScript VM instance (Выбрать экземпляр виртуальной машины JavaScript).
  2. Нажмите кнопку Пуск.
  3. Выполните на странице действия, которые вы хотите изучить.
  4. Когда вы закончите, нажмите кнопку Остановить.

DevTools показывает разбивку распределения памяти по функциям. По умолчанию используется представление Heavy (Bottom Up) (Тяжелые (снизу вверх)), в котором функции, выделившие больше всего памяти, отображаются вверху.

Страница результатов профиля распределения.

Как определить объекты, которые сохраняются с помощью ссылки JS

В профиле Отключенные элементы показаны отключенные элементы, которые сохраняются, потому что на них ссылается код JavaScript.

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

Пример профиля отключенных элементов.

Как выявить частые сборки мусора

Если страница часто приостанавливается, возможно, у вас проблемы со сборкой мусора.

Чтобы обнаружить частые сборки мусора, можно использовать Диспетчер задач Chrome или записи памяти на временной шкале. Частое повышение и понижение значений Память или Память JavaScript в Диспетчере задач указывает на частую сборку мусора. Если на графиках в записях временной шкалы часто поднимается и падает количество узлов или куча JS, это указывает на частые сборки мусора.

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