Исправить проблемы с памятью

Кейс Баскс
Kayce Basques

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

Краткое содержание

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

Обзор

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

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

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

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

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

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

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

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

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

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

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

  2. Щелкните правой кнопкой мыши заголовок таблицы диспетчера задач и включите функцию «Память JavaScript» .

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

Эти два столбца сообщают вам разную информацию о том, как ваша страница использует память:

  • Столбец Memory footprint представляет память ОС. Узлы DOM хранятся в памяти ОС. Если это значение увеличивается, узлы DOM создаются.
  • Столбец JavaScript Memory представляет кучу JS. Этот столбец содержит два значения. Значение, которое вас интересует, — это живое число (число в скобках). Живое число показывает, сколько памяти используют доступные объекты на вашей странице. Если это число увеличивается, то либо создаются новые объекты, либо существующие объекты растут.

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

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

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

  1. Откройте панель «Производительность» в DevTools.
  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 . Запуск этого кода создает запись Timeline, как на следующем снимке экрана:

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

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

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

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

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

Вот простой пример отсоединенных узлов 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 вашей страницы в момент снимка.

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

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

Обработка и загрузка снимка может занять некоторое время. После завершения выберите его на левой панели (с именем Heap snapshots ).

Введите Detached в поле ввода фильтра классов для поиска отсоединенных деревьев DOM.

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

Расширьте караты, чтобы исследовать отдельно стоящее дерево.

Исследование отдельно стоящего дерева.

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

Исследование оторванного узла.

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

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

Для демонстрации временной шкалы распределения рассмотрим следующий код:

var x = [];

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

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

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

Чтобы записать временную шкалу распределения , откройте DevTools, перейдите на панель Memory , выберите переключатель Allocations on timeline , нажмите кнопку Record , выполните действие, которое, по вашему мнению, вызывает утечку памяти, а затем нажмите Когда закончите, нажмите кнопку «Остановить запись» .

Во время записи обратите внимание на появление синих полос на временной шкале распределения , как на следующем снимке экрана.

Новые распределения в шкале производительности.

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

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

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

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

Исследовать распределение памяти по функциям

Используйте тип профиля выборки распределения на панели «Память» для просмотра распределения памяти по функции JavaScript.

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

  1. Выберите переключатель Allocation sampling . Если на странице есть worker, вы можете выбрать его в качестве цели профилирования в окне Select JavaScript VM instance .
  2. Нажмите кнопку «Пуск» .
  3. Выполните действия на странице, которую вы хотите исследовать.
  4. Нажмите кнопку «Стоп» , когда закончите все действия.

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

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

Определите объекты, сохраненные ссылкой JS

Профиль «Отсоединенные элементы» показывает отсоединенные элементы, которые сохраняются, поскольку на них ссылается код JavaScript.

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

Пример профиля отдельно стоящих элементов.

Отмечайте частые вывозы мусора

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

Вы можете использовать либо Chrome Task Manager, либо записи памяти Timeline, чтобы обнаружить частые сборки мусора. В Task Manager часто растущие и падающие значения Memory или JavaScript Memory представляют частые сборки мусора. В записях Timeline часто растущие и падающие графики JS heap или node count указывают на частые сборки мусора.

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