Анализ принципа восстановления памяти памяти двигателя V8

внешний интерфейс JavaScript

В отличие от C/C++, который позволяет программистам самостоятельно открывать или освобождать память, язык JS похож на Java, который использует собственный набор алгоритмов сборки мусора для автоматического управления памятью. Как старшему фронтенд-инженеру, вам нужно очень четко понимать механизм перезапуска памяти JS, чтобы вы могли анализировать узкие места производительности системы в экстремальных условиях. Этот механизм также очень помогает нашему глубокому пониманию характеристик закрытия JS и эффективного использования памяти.

Лимит памяти V8

В других back-end языках, таких как Java/Go, ограничений на использование памяти нет, но JS отличается, V8 может использовать только часть системной памяти, конкретно в64Под битовой системой, V8 может только назначить только1.4G, в 32-битной системе можно назначить не более0.7G. Если подумать о больших требованиях к памяти во фронтенде, то это не так уж и много, но для бэкэнда, если nodejs встретит Файл размером более 2Г, то он не сможет прочитать его весь в память для различных операций.

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

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

Итак, вопрос в том, почему V8 устанавливает для него предел памяти? Очевидно, что у моей машины десятки гигабайт памяти, поэтому я могу использовать только столько?

По сути, это определяется двумя факторами: одним из них является механизм выполнения одного потока JS, а другим — ограничение механизма сборки мусора JS.

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

Взяв в качестве примера 1,5 ГБ кучи памяти для сборки мусора, V8 требуется более 50 мс для выполнения небольшой сборки мусора и даже более 1 с для выполнения неинкрементной (ps: будет объяснено позже) сборки мусора.

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

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

// 这是调整老生代这部分的内存,单位是MB。后面会详细介绍新生代和老生代内存
node --max-old-space-size=2048 xxx.js 

или

// 这是调整新生代这部分的内存,单位是 KB。
node --max-new-space-size=2048 xxx.js

Переработка памяти нового поколения

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

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

Первый это память нового поколения.Только что введен метод настройки памяти нового поколения.Какой предел по умолчанию у его памяти? 32 МБ и 16 МБ в 64-битной и 32-битной системах соответственно. Достаточно маленький, но все же хороший Понятно, что переменные в новом поколении имеют короткое время выживания, и они исчезнут, как только появятся, и нелегко создать слишком большую нагрузку на память, поэтому ее можно установить достаточно малой.

Ну и как работает сборщик мусора нового поколения?

Сначала разделите пространство памяти нового поколения на два:

Часть From представляет используемую память, а To — незанятую в данный момент память.

При сборке мусора V8 проверяет объект в части From, если это уцелевший объект, то он копируется в память To (заменяется по порядку в памяти To), а если это не уцелевший объект, то могут быть непосредственно переработаны.

Когда все уцелевшие объекты в From входят в память To по порядку, роли как From, так и To对调, From сейчас простаивает, To используется и так далее.

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

Обратите внимание, что я только что указал, что память To размещается по порядку с самого начала, что должно иметь дело с таким сценарием:

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

Алгоритм очистки в основном предназначен для решения проблемы фрагментации памяти после создания копии еды, чтобы стать таким пространством:

Это намного аккуратнее? Это значительно облегчает последующее выделение сплошного пространства.

Однако недостаток алгоритма Scavenge также весьма очевиден, то есть память может использовать только половину памяти нового поколения, но в ней хранятся только объекты с коротким жизненным циклом.一般很少,следовательно时间Производительность отличная.

Переработка памяти старого поколения

Метод рециркуляции нового поколения только что был введен.Если переменные в новом поколении все еще существуют после многократной рециркуляции, они будут помещены в老生代内存, это явление называется晋升.

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

  • Один раз прошел восстановление Scavenge.
  • Использование памяти для (свободного) пространства превышает 25%.

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

Итак, какая стратегия сбора мусора используется старым поколением?

Первым шагом является отметка-очистить. Этот процесс подробно описан в «Расширенном программировании на JavaScript (третье издание)» и в основном разделен на два этапа, а именно этап маркировки и этап очистки. Сначала он обходит все объекты в куче, отметить их, а затем для среды кода使用的变量и по强引用Переменная не помечена, а остальная часть является удаляемой переменной.清除阶段Восстановите его пространство.

Конечно, это приведет к проблеме фрагментации памяти, а разрыв пространства уцелевших объектов создаст препятствия для последующего выделения пространства. Как старое поколение решало эту проблему?

Второй шаг — дефрагментация памяти. Решение V8 очень простое и грубое: после завершения этапа зачистки все уцелевшие объекты перемещаются ближе к одному концу.

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

Инкрементные маркеры

Из-за однопоточного механизма JS, когда V8 выполняет сборку мусора, это неизбежно блокирует выполнение бизнес-логики.Если задача сборки мусора старого поколения тяжелая, то времязатратность будет ужасной, что серьезно повлияет производительность приложения. В настоящее время, чтобы избежать этой проблемы, V8 использует схему поэтапной маркировки, то есть задача маркировки, выполненная за один раз, делится на множество мелких частей для завершения, и после завершения каждой маленькой части она «ломается» и выполняет логику js-приложения некоторое время, Затем выполните следующую часть.Если цикл завершен, он не войдет в фрагментацию памяти, пока не будет завершена фаза маркировки. На самом деле этот процесс немного похож на идею React Fiber, поэтому здесь он не будет распространяться.

После инкрементной разметки время блокировки JS-приложения в процессе сборки мусора сократилось до 1/6, видно, что это очень удачное улучшение.

Здесь представлен принцип сборки мусора JS, на самом деле он очень прост для понимания, главное понять его.为什么要这么做, не просто如何做的, я надеюсь, что это резюме может вдохновить вас.

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

Продвинутое программирование на JavaScript (третье издание)

«Глубокие и простые узлы» от Park Ling

Geek Time "Принцип работы и практика браузеров"

Категории