Глубокое понимание механизма сборки мусора Chrome V8.

JavaScript

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

  • JavaScriptКакова конкретная форма хранения переменных в памяти?
  • Механизм сборки мусора в браузере, как избежать утечек памяти?

Потом я поискал различную информацию и составил эту статью.

Прочитав эту статью, вы сможете узнать:

  • Как управляется память JavaScript?
  • Как Chrome выполняет сборку мусора?
  • Какие оптимизации делает Chrome для сборки мусора?

исходный адресприветственная звезда

Управление памятью JavaScript

Независимо от языка программирования жизненный цикл памяти в основном одинаков:

  1. Выделите необходимую память
  2. Использовать выделенную память (чтение, запись)
  3. Отпустите его, когда он не нужен\возвращается

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

var n = 123; // 给数值变量分配内存
var s = "azerty"; // 给字符串分配内存

var o = {
  a: 1,
  b: null
}; // 给对象及其包含的值分配内存

// 给数组及其包含的值分配内存(就像对象一样)
var a = [1, null, "abra"]; 

function f(a){
  return a + 2;
} // 给函数(可调用的对象)分配内存

// 函数表达式也能分配一个对象
someElement.addEventListener('click', function(){
  someElement.style.backgroundColor = 'blue';
}, false);

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

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

{}=={} // false
[]==[] // false
''=='' // true

В JavaScript типы данных делятся на две категории: простые типы и ссылочные типы.Для простых типов память хранится в пространстве стека, а для сложных типов данных память хранится в куче.

  • Базовые типы: эти типы занимают место фиксированного размера в памяти, а их значения хранятся в пространстве стека, к которому мы обращаемся по значению.
  • Тип ссылки: тип ссылки, размер значения не фиксирован, а адрес хранения в памяти стека указывает на объект в памяти кучи. доступ осуществляется по ссылке.

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

Зачем нужна сборка мусора

В Chrome v8 ограничено использование памяти (64-разрядная версия — около 1,4 ГБ/1464 МБ, 32-разрядная — около 0,7 ГБ/732 МБ), зачем ее ограничивать?

  1. Поверхностная причина в том, что V8 изначально разрабатывался для браузеров и вряд ли столкнется со сценариями, использующими много памяти.
  2. Основной причиной является ограничение механизма сборки мусора V8 (если на очистку большого количества мусора в памяти уходит много времени, из-за чего поток JavaScript приостанавливает время выполнения, производительность и приложение резко падают).

Как упоминалось ранее, память в стеке автоматически выделяется и освобождается операционной системой, а память в куче освобождается вручную движком JS (например, Chrome V8).Когда наш код написан неправильно, он будет в результате механизм сборки мусора движка JS не может должным образом освободить память (memory leak), так что память, занимаемая браузером, продолжает увеличиваться, что, в свою очередь, приводит к деградации производительности JavaScript, приложений и операционной системы. системы.

Алгоритм сборки мусора Chrome

На самом деле в JavaScript у большинства объектов очень короткий жизненный цикл.Большая часть объектов будет освобождена после сборки мусора, а небольшое количество объектов будет иметь долгий жизненный цикл и всегда будут активными объектами. быть переработаны. Для повышения эффективности рециркуляции V8 делит кучу на две категории.新生代а также老生代, объекты с коротким временем существования сохраняются в новом поколении, а объекты с большим временем существования сохраняются в старом поколении.

Новая область обычно поддерживает только емкость от 1 до 8 МБ, в то время как старая область поддерживает гораздо большую емкость. Для этих двух регионов V8 использует два разных сборщика мусора для более эффективной реализации сбора мусора.

  • Вторичный сборщик мусора — Scavenge: в основном отвечает за сбор мусора нового поколения.
  • Основной сборщик мусора — Mark-Sweep и Mark-Compact: в основном отвечает за сборку мусора старого поколения.

Сборщик мусора молодого поколения - Scavenge

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

Алгоритм Scavange делит кучу молодого поколения на две части, называемыеfrom-spaceа такжеto-space, метод работы также очень прост, то естьfrom-spaceЖивые объекты, сохранившиеся в копииto-space, и упорядочить память об этих объектах, а затемfrom-spaceПамять неактивных объектов освобождается, и когда это делается,from spaceа такжеto spaceПоменяйте их местами, чтобы можно было повторно использовать два региона в молодом поколении.

Простое описание:

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

Итак, как сборщик мусора узнает, какие объекты активны, а какие неактивны?

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

Когда объект молодого поколения становится объектом старого поколения?

В новом поколении он подразделяется наnurseryпотомство иintermediateВ дочернем поколении есть две области: когда объект выделяет память в первый раз, она будет выделена новому поколению.nurseryДети, если объект все еще находится в новом поколении после очередной сборки мусора, то переходим кintermediateРебенок, после следующей сборки мусора, если объект все еще находится в молодом поколении, вторичный сборщик мусора переместит объект в старое поколение.Этот процесс перемещения называется продвижением.

Сборка мусора старого поколения — Mark-Sweep и Mark-Compact

После того, как объекты в пространстве нового поколения удовлетворяют определенным условиям, они переходят в пространство старого поколения.Объекты в пространстве старого поколения уже прошли хотя бы одну или несколько переработок, поэтому вероятность их выживания будет выше.Если вы его используете снова в это времяscavengeЕсть две проблемы с алгоритмом:

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

Поэтому алгоритмы Mark-Sweep (удаление меток) и Mark-Compact (завершение меток) используются в пространстве старого поколения.

Mark-Sweep

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

  • Фаза маркировки: первое сканирование старого поколения, маркировка живых объектов
  • Фаза очистки: выполните второе сканирование на старом поколении, чтобы удалить немаркированные объекты, то есть очистить неактивные объекты.

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

Mark-Compact

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

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

Полная остановка Stop-The-World

Так как сборка мусора выполняется в движке JS, а алгоритму Mark-Compact необходимо перемещать объекты во время выполнения, когда активных объектов много, скорость его выполнения не может быть очень быстрой, чтобы избежать логики приложения JavaScript и сборки мусора. из-за конкуренции ресурсов памяти GC сборщик мусора приостановит приложение JavaScript.Этот процесс называется全停顿(остановить мир).

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

Оптимизация Ориноко

orinoco — это кодовое название проекта сборщика мусора V8.Чтобы улучшить взаимодействие с пользователем и решить проблему полной паузы, он использует инкрементную маркировку, ленивую очистку, параллелизм и параллелизм для сокращения времени приостановки основного потока.

Инкрементная маркировка - Инкрементная маркировка

Чтобы сократить время паузы при сборке мусора полной кучи, инкрементная маркировка разбивает исходные помеченные объекты полной кучи на задачи, которые перемежаются логикой приложения JavaScript для выполнения.Это позволяет сделать паузу 5–10 мс при маркировке кучи. . Инкрементальная маркировка включается, когда размер кучи достигает определенного порога.После включения, каждый раз при выделении определенного объема памяти выполнение скрипта будет приостанавливаться и выполняться инкрементная маркировка.

Ленивое подметание - Ленивое подметание

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

Появление добавочной разметки и ленивой очистки сокращает максимальное время паузы основного потока на 80%, делая взаимодействие между пользователем и браузером намного более плавным.Из механизма реализации, за счет выполнения между каждым небольшим добавочным кодом кода JavaScript, указатель объекта в куче мог измениться, вам нужно использовать写屏障технология для записи изменений в этих ссылочных отношениях, поэтому она также выявляет недостатки добавочной разметки:

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

Одновременно - одновременно

Одновременный GC не требуется, чтобы основной поток зависал во время сбора мусора, оба из которых могут быть выполнены одновременно, и только когда индивидуума нуждается в короткой паузе, чтобы позволить сборщику мусора сделать что-то специальные операции. Тем не менее, этот подход должен также столкнуться с проблемой инкрементного восстановления, то есть процесс сбора мусора, поскольку выполнение кода JavaScript, процитировало отношения объекта куча могут в любое время, поэтому они будут写屏障работать.

Параллельно - Параллельно

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

Текущий механизм сборки мусора V8

В 2011 году V8 применил механизм пошаговой маркировки. До 2018 года в Chrome64 и Node.js V10 начали параллельную разметку (Concurrent) и добавили параллельную (Parallel) технологию на основе параллелизма, что значительно сократило время сборки мусора.

Вторичный сборщик мусора

В сборке мусора нового поколения V8 использует параллельный механизм: на этапе сортировки активные объекты удаляются изfrom-toскопировать вspace-toВ то же время разрешите параллельно организовать несколько вспомогательных потоков. Поскольку несколько потоков конкурируют за ресурсы памяти кучи нового поколения, может возникнуть проблема, связанная с тем, что активный объект копируется несколькими потоками.Чтобы решить эту проблему, V8 копирует активный объект в первый поток, и копирование завершается. После этого необходимо поддерживать адрес пересылки указателя после копирования активного объекта, чтобы другие вспомогательные потоки могли определить, был ли скопирован активный объект после нахождения активного объекта.

главный сборщик мусора

В сборке мусора старого поколения V8, если размер памяти в куче превышает определенный порог, будет включена параллельная (Concurrent) задача маркировки. Каждый вспомогательный поток будет отслеживать указатель каждого отмеченного объекта и ссылку на этот объект, а при выполнении кода JavaScript параллельная разметка также осуществляется в фоновом вспомогательном процессе. код изменен, барьер записи (write barriers) отслеживает, когда вторичные потоки маркируются одновременно.

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

конец

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

использованная литература