В этом разделе описаны общие термины, используемые при анализе памяти, и он применим к различным инструментам профилирования памяти для разных языков.
Описанные здесь термины и понятия относятся к Chrome DevTools Heap Profiler . Если вы когда-либо работали с Java, .NET или каким-либо другим профилировщиком памяти, это может быть вам полезно.
Размеры объекта
Думайте о памяти как о графе с примитивными типами (например, числами и строками) и объектами (ассоциативными массивами). Визуально это можно представить в виде графика с рядом взаимосвязанных точек следующим образом:
Объект может хранить память двумя способами:
- Непосредственно самим объектом.
- Неявно, сохраняя ссылки на другие объекты и, следовательно, предотвращая автоматическое удаление этих объектов сборщиком мусора (сокращенно GC ).
При работе с профилировщиком кучи в DevTools (инструмент для исследования проблем с памятью, расположенный на панели «Память» ) вы, скорее всего, обнаружите, что просматриваете несколько разных столбцов информации. Два из них выделяются — Shallow Size и Retained Size , но что они означают?
Мелкий размер
Это размер памяти, занимаемой самим объектом.
Типичные объекты JavaScript имеют некоторую память, зарезервированную для их описания и хранения непосредственных значений. Обычно только массивы и строки могут иметь значительный небольшой размер. Однако строки и внешние массивы часто имеют основное хранилище в памяти средства рендеринга, предоставляя лишь небольшой объект-оболочку в куче JavaScript.
Память рендерера — это вся память процесса, в котором отображается проверяемая страница: собственная память + куча памяти JS страницы + куча памяти JS всех выделенных рабочих процессов, запущенных страницей. Тем не менее, даже небольшой объект может косвенно удерживать большой объем памяти, предотвращая удаление других объектов в процессе автоматической сборки мусора.
Сохраненный размер
Это размер памяти, которая освобождается после удаления самого объекта вместе с его зависимыми объектами, которые стали недоступными из корней GC .
Корни GC состоят из дескрипторов , которые создаются (локальные или глобальные) при создании ссылки из собственного кода на объект JavaScript за пределами V8. Все такие дескрипторы можно найти в снимке кучи в разделе «Корни GC» > «Область дескриптора» и «Корни GC» > «Глобальные дескрипторы» . Описание дескрипторов в этой документации без подробностей реализации браузера может привести к путанице. Вам не стоит беспокоиться ни о корнях GC, ни о дескрипторах.
Существует множество внутренних корней GC, большинство из которых не интересны пользователям. С точки зрения приложений существуют следующие виды корней:
- Глобальный объект окна (в каждом iframe). В снимках кучи есть поле расстояния, которое представляет собой количество ссылок на свойства на кратчайшем пути сохранения из окна.
- Дерево DOM документа, состоящее из всех собственных узлов DOM, доступных при просмотре документа. Не у всех из них могут быть оболочки JS, но если они есть, оболочки будут живы, пока жив документ.
- Иногда объекты могут сохраняться контекстом отладчика и консолью DevTools (например, после оценки консоли). Создавайте снимки кучи с чистой консолью и без активных точек останова в отладчике.
Граф памяти начинается с корня, которым может быть объект window
браузера или Global
объект модуля Node.js. Вы не контролируете, как этот корневой объект будет GC.
Все, что недоступно из корня, получает GC.
Объекты, сохраняющие дерево
Куча — это сеть взаимосвязанных объектов. В математическом мире эта структура называется графом или графом памяти. Граф состоит из узлов , соединенных ребрами , каждому из которых присвоены метки.
- Узлы ( или объекты ) помечаются именем функции- конструктора , которая использовалась для их создания.
- Ребра помечаются именами свойств .
Узнайте , как записать профиль с помощью Heap Profiler . Некоторые из привлекательных вещей, которые мы можем увидеть в следующей записи профилировщика кучи, включают расстояние: расстояние от корня GC. Если почти все объекты одного типа находятся на одинаковом расстоянии, а некоторые — на большем, это стоит изучить.
Доминаторы
Объекты-доминаторы имеют древовидную структуру, поскольку каждый объект имеет ровно одного доминатора. У доминанта объекта может отсутствовать прямые ссылки на объект, над которым он доминирует; то есть дерево доминатора не является остовным деревом графа.
На следующей схеме:
- Узел 1 доминирует над узлом 2
- Узел 2 доминирует над узлами 3, 4 и 6.
- Узел 3 доминирует над узлом 5
- Узел 5 доминирует над узлом 8
- Узел 6 доминирует над узлом 7
В следующем примере узел #3
является доминатором узла #10
, но #7
также существует на каждом простом пути от GC до #10
. Следовательно, объект B является доминатором объекта A, если B существует на каждом простом пути от корня до объекта A.
Особенности V8
При профилировании памяти полезно понимать, почему снимки кучи выглядят определенным образом. В этом разделе описаны некоторые темы, связанные с памятью, специально относящиеся к виртуальной машине JavaScript V8 (VM V8 или VM).
Представление объекта JavaScript
Существует три примитивных типа:
- Числа (например, 3.14159..)
- Логические значения (истина или ложь)
- Струнные (например, «Вернер Гейзенберг»)
Они не могут ссылаться на другие значения и всегда являются листьями или завершающими узлами.
Числа могут храниться как:
- непосредственные 31-битные целые значения, называемые малыми целыми числами ( SMI ), или
- объекты кучи, называемые числами кучи . Числа кучи используются для хранения значений, которые не вписываются в форму SMI, например, double , или когда значение необходимо упаковать , например, для установки его свойств.
Строки могут храниться в:
- куча виртуальной машины или
- внешне в памяти рендерера . Объект-оболочка создается и используется для доступа к внешнему хранилищу, где, например, хранятся источники сценариев и другой контент, полученный из Интернета, а не копируется в кучу виртуальной машины.
Память для новых объектов JavaScript выделяется из выделенной кучи JavaScript (или кучи VM ). Эти объекты управляются сборщиком мусора V8 и, следовательно, будут оставаться активными до тех пор, пока на них есть хотя бы одна сильная ссылка.
Нативные объекты — это все остальное, чего нет в куче JavaScript. Собственный объект, в отличие от объекта кучи, не управляется сборщиком мусора V8 на протяжении всего своего существования, и доступ к нему возможен только из JavaScript с использованием его объекта-оболочки JavaScript.
Строка Cons — это объект, состоящий из пар строк, сохраненных, а затем объединенных, и являющийся результатом конкатенации. Объединение содержимого строки cons происходит только по мере необходимости. Примером может служить ситуация, когда необходимо создать подстроку объединенной строки.
Например, если вы объедините a и b , вы получите строку (a, b), которая представляет результат объединения. Если позже вы объедините d с этим результатом, вы получите еще одну строку cons ((a, b), d).
Массивы. Массив — это объект с числовыми ключами. Они широко используются в виртуальной машине V8 для хранения больших объемов данных. Наборы пар ключ-значение, используемые как словари, резервируются массивами.
Типичный объект JavaScript может быть одним из двух типов массива, используемых для хранения:
- именованные свойства и
- числовые элементы
В случаях, когда имеется очень небольшое количество свойств, их можно хранить внутри самого объекта JavaScript.
Карта – объект, описывающий тип объекта и его расположение. Например, карты используются для описания неявных иерархий объектов для быстрого доступа к свойствам .
Группы объектов
Каждая группа собственных объектов состоит из объектов, которые содержат взаимные ссылки друг на друга. Рассмотрим, например, поддерево DOM, где каждый узел имеет ссылку на своего родителя и ссылки на следующего дочернего элемента и следующего родственного узла, образуя таким образом связный граф. Обратите внимание, что собственные объекты не представлены в куче JavaScript — поэтому они имеют нулевой размер. Вместо этого создаются объекты-обертки.
Каждый объект-оболочка содержит ссылку на соответствующий собственный объект для перенаправления на него команд. В свою очередь, группа объектов содержит объекты-оболочки. Однако это не создает несобираемого цикла, поскольку GC достаточно умен, чтобы освободить группы объектов, на оболочки которых больше нет ссылок. Но если вы забудете освободить одну оболочку, будет удержана вся группа и связанные с ней оболочки.