предисловие
Мы знаем, что причина, по которой JavaScript может работать в среде браузера и среде NodeJS, заключается в том, что движок V8 находится за кулисами. Он неотделим от всего процесса компиляции, выделения памяти, выполнения и сборки мусора.
Прежде чем писать эту статью, я также прочитал много блогов в Интернете, в том числе некоторый оригинальный контент на английском языке, поэтому я хочу сделать краткое изложение этой статьи, добавив свое собственное мышление и блок-схему, сделанную вручную ~ ~
Я надеюсь, что эта статья поможет вам, и эта статья также будет включена в мою собственнуюперсональный сайт.
Зачем собирать мусор
В языке C и языке C++, если мы хотим открыть часть памяти кучи, нам нужно сначала вычислить размер требуемой памяти, а затем передатьmallocФункция для ручного выделения, после ее использования не забывайте использовать ее всегдаfreeфункцию очистки и освобождения, иначе эта память будет постоянно занята, что приведет к утечке памяти.
Но когда мы пишем JavaScript, у нас нет этого процесса, потому что другие уже упаковали его для нас, и движок V8 автоматически применит и выделит память в соответствии с размером объекта, который вы сейчас определяете.
Нам не нужно вручную управлять памятью, поэтому естественно иметьвывоз мусора, в противном случае она будет только выделена, но не переработана.Не потребуется ли много времени, чтобы память заполнилась, что приведет к сбою приложения.
Преимущество сборки мусора в том, что она не требует от нас управления памятью и больше внимания уделяет реализации сложных приложений, но из этого же вытекает и недостаток.Без управления можно не обращать внимания при написании кода, что приводит к циклическим ссылкам, и т. д. ситуация, приводящая к утечке памяти.
выделение структуры памяти
Поскольку V8 изначально создавался для выполнения JavaScript в браузере, маловероятно, что он столкнется со сценариями, использующими много памяти, поэтому максимальный объем памяти, на который он может претендовать, установлен не слишком большой, около 1,4 ГБ в 64-битных системах, и около 700 МБ в 32-битной системе.
В среде NodeJS мы можем просмотреть распределение памяти с помощью **process.memoryUsage()**.
process.memoryUsageВозвращает объект, содержащий объем памяти процесса Node. Объект содержит четыре поля со следующими значениями:
rss(resident set size):所有内存占用,包括指令区和堆栈
heapTotal:V8引擎可以分配的最大堆内存,包含下面的 heapUsed
heapUsed:V8引擎已经分配使用的堆内存
external: V8管理C++对象绑定到JavaScript对象上的内存
Все вышеперечисленные единицы памяти являются байтами (Byte).
Если вы хотите расширить пространство памяти, доступное для Node, вы можете использоватьBufferЖду памяти вне кучи, здесь подробно объяснять не буду.Если вам интересно, то можете перейти к некоторой информации.
Ниже приведена общая схема архитектуры Node, которая поможет вам понять вышеизложенное:
Node Standard Library: 是我们每天都在用的标准库,如Http, Buffer 模块
Node Bindings: 是沟通JS 和 C++的桥梁,封装V8和Libuv的细节,向上层提供基础API服务
第三层是支撑 Node.js 运行的关键,由 C/C++ 实现:
1. V8 是Google开发的JavaScript引擎,提供JavaScript运行环境,可以说它就是 Node.js 的发动机
2. Libuv 是专门为Node.js开发的一个封装库,提供跨平台的异步I/O能力
3. C-ares:提供了异步处理 DNS 相关的能力
4. http_parser、OpenSSL、zlib 等:提供包括 http 解析、SSL、数据压缩等其他的能力
механизм сбора мусора
1. Как определить, можно ли его переработать
1.1 Удаление тегов
Когда переменная попадает в среду (например, при объявлении переменной в функции), переменная помечается как «входящая в среду». Логически память, занимаемая переменными, входящими в среду, никогда не может быть освобождена, потому что они могут быть использованы, как только поток выполнения входит в соответствующую среду. И когда переменная покидает среду, она помечается как «выходящая из среды».
Переменные могут быть помечены любым способом. Например, вы можете записать, когда переменная входит в среду, перевернув специальный бит, или использовать список переменных «в среду» и список переменных «вне среды», чтобы отслеживать, какая переменная изменилась. Неважно, как вы обозначите переменные, важно, какую стратегию вы выберете.
- (1) Сборщик мусора пометит все переменные, хранящиеся в памяти, во время выполнения (конечно, можно использовать любой метод маркировки).
- (2) Затем он удаляет теги переменных в среде выполнения и переменных, на которые ссылаются переменные в среде.
- (3) После этого переменные, которые все еще помечены, считаются переменными, подлежащими удалению, поскольку эти переменные больше не доступны в среде выполнения.
- (4) Наконец, сборщик мусора завершает очистку памяти, уничтожает отмеченные значения и освобождает место в памяти, которое они занимают.
В настоящее время реализации JavaScript для IE, Firefox, Opera, Chrome и Safari используют стратегию сборки мусора пометки и очистки (или аналогичную стратегию), но с разными интервалами между сборками мусора.
Активный объектЭто корень выше. Если вы не знаете активный объект, вы можете сначала проверить данные. Когда объект и связанные с ним объекты больше не ссылаются на текущий корень через отношение ссылки, объект будет удален сборщиком мусора.
1.2 Подсчет ссылок
Стратегии сборки мусора с подсчетом ссылок менее распространены. Смысл в том, чтобы отслеживать, сколько раз упоминается каждое значение. Когда переменная объявляется и ей присваивается значение ссылочного типа, количество ссылок на это значение равно 1.
Если такое же значение присваивается другой переменной, количество ссылок на это значение увеличивается на 1. И наоборот, если переменная, содержащая ссылку на это значение, изменяет объект, на который указывает ссылка, количество ссылок на это значение уменьшается на 1.
Когда количество ссылок на это значение становится равным 0, это означает, что нет возможности получить доступ к этому значению, поэтому занимаемое им пространство памяти может быть восстановлено.
Таким образом, когда сборщик мусора запустится в следующий раз, он освободит память, занятую значениями, счетчик ссылок которых равен 0.
Netscape Navigator 3.0 был первым браузером, использующим стратегию подсчета ссылок, но вскоре он столкнулся с серьезной проблемой:циклическая ссылка.
Циклическая ссылка означает, что объект A содержит указатель на объект B, а объект B также содержит ссылку на объект A, см. пример:
function foo () {
var objA = new Object();
var objB = new Object();
objA.otherObj = objB;
objB.anotherObj = objA;
}
В этом примере objA и objB ссылаются друг на друга через свои соответствующие свойства, то есть количество ссылок на оба объекта равно 2.
В реализации со стратегией маркировки-очистки эта взаимная ссылка не является проблемой, поскольку оба объекта выходят за пределы области видимости после выполнения функции.
Но в реализации, использующей стратегию подсчета ссылок, после выполнения функции objA и objB продолжат существовать, потому что их счетчик ссылок никогда не будет равен 0.
Если эта функция вызывается повторно, большой объем памяти не будет освобожден. С этой целью Netscape также отказалась от метода подсчета ссылок в Navigator 4.0 и обратилась к пометке и очистке, чтобы реализовать свой механизм сборки мусора.
Следует также отметить, что большинство из нас все время пишут циклический справочный код.Посмотрите на следующий пример, я думаю, что все писали так:
var el = document.getElementById('#el');
el.onclick = function (event) {
console.log('element was clicked');
}
Мы привязываем анонимную функцию к событию щелчка элемента, мы передаемeventПараметр должен получить соответствующий элементelинформации.
Подумайте об этом, это просто циклическая ссылка?elимеет атрибутonclickСсылка на функцию (фактически на объект) и на параметры в функцииel,такelКоличество ссылок всегда равно 2, и даже если текущая страница закрыта, сборка мусора не может быть выполнена.
Если таких методов записи много, это вызовет утечку памяти. Мы можем сделать это, очистив ссылку на событие, когда страница выгружается, чтобы ее можно было переработать:
var el = document.getElementById('#el');
el.onclick = function (event) {
console.log('element was clicked');
}
// ...
// ...
// 页面卸载时将绑定的事件清空
window.onbeforeunload = function(){
el.onclick = null;
}
Политика сборки мусора V8
Алгоритмов автоматической сборки мусора существует множество, из-за разного жизненного цикла разных объектов решить задачу одной стратегией сборки невозможно, что будет очень неэффективно.
Поэтому V8 использует стратегию повторного использования поколений, разделяя память на два поколения:новое поколениеа такжестарое поколение.
Объекты в новом поколении - это объекты с коротким временем выживания, а объекты в старом поколении - это объекты с большим временем выживания или находящиеся в памяти.Для нового и старого поколений используются разные алгоритмы сборки мусора для повышения эффективности.В первую очередь выделяется новому поколению (если места памяти нового поколения недостаточно, оно сразу выделяется старому поколению), и объекты нового поколения будут перемещены в старое поколение после выполнения определенных условий.Этот процесс также называется поощрением, о чем я подробно расскажу позже.
Память поколений
По умолчанию размер памяти нового поколения 32-битной системы составляет 16 МБ, а размер памяти старого поколения — 700 МБ, в 64-битной системе размер памяти нового поколения — 32 МБ, а размер памяти старого поколения — 1,4 ГБ.
Новое поколение равномерно разделено на два равных пространства памяти, называемых полупространствами, каждое из которых имеет размер 8 МБ (32-разрядная версия) или 16 МБ (64-разрядная версия).
Кайнозой
1. Способ распространения
Новое поколение хранит объекты с коротким жизненным циклом, и память легко выделить.Просто сохраните указатель на место в памяти и увеличьте указатель в соответствии с размером выделенного объекта.Когда место для хранения почти заполнено, сборка мусора производится. .
2. Алгоритмы
Принятие нового поколенияScavengeАлгоритм сборки мусора, который в основном используется при реализации алгоритмаCheneyалгоритм.
Алгоритм Чейни делит память на две части, называемые полупространствами, одна из которых используется, а другая простаивает.
Используемое полупространство называетсяИз космоса, полупространство в незанятом состоянии называетсяВ космос.
Я нарисовал набор подробных блок-схем, а затем объединил блок-схемы, чтобы подробно объяснить, как работает алгоритм Чейни. Вывоз мусора в дальнейшем именуется собирательноGC (сборка мусора).
step13 объекта A, B, C размещены в пространстве From
step2GC входит, чтобы определить, что объект B не имеет других ссылок и может быть переработан, а объекты A и C по-прежнему являются активными объектами.
step3, Скопируйте активные объекты A и C из пространства From в пространство To.
step4, Очистить всю память Из космоса
step5, Поменять местами из космоса и в космос
step6, Добавлено 2 новых объекта D и E в пространстве From
step7, Следующий раунд GC приходит и обнаруживает, что объект D больше не упоминается, пометьте его
step8Скопируйте активные объекты A, C, E из пространства From в пространство To.
step9, Очистить всю память Из космоса
step10, Продолжайте обмениваться местами из космоса и в космос, чтобы начать следующий раунд.
Из приведенной выше блок-схемы ясно видно, что цель обмена From и To состоит в том, чтобы удерживать активный объект в одном полупространстве, а другое полупространство в состоянии ожидания.
Поскольку Scavenge копирует только уцелевшие объекты и учитывает только небольшую часть уцелевших объектов в сценах с короткими жизненными циклами, у него отличная эффективность времени. Недостатком Scavenge является то, что он может использовать только половину кучи памяти, которая определяется пространством раздела и механизмом копирования.
Поскольку Scavenge — это типичный алгоритм, жертвующий пространством ради времени, его нельзя применять ко всем сборкам мусора в больших масштабах. Но мы видим, что Scavenge очень подходит для применения в новом поколении, потому что жизненный цикл объектов в новом поколении короткий, что как раз подходит для этого алгоритма.
3. Продвижение
Когда объект выживает в нескольких копиях, он считается долгоживущим объектом. Затем такие долгоживущие объекты переводятся в старое поколение и управляются с использованием нового алгоритма.
Процесс перемещения объектов от молодого поколения к старому называется продвижением..
Есть два основных условия продвижения объекта:
-
Когда объект копируется из пространства "Откуда" в пространство "В", его адрес памяти проверяется, чтобы определить, подвергался ли объект восстановлению очистки. Если он был испытан, объект будет перемещен из пространства From в пространство старого поколения, если нет, он будет скопирован в пространство To.Таким образом, если объект копируется из пространства «Откуда» в пространство «В» во второй раз, то объект будет перемещен в старое поколение..
-
При копировании объекта из пространства «Откуда» в пространство «В», если пространство «В» было использовано более чем на 25%, объект напрямую повышается до старого поколения. Причина установки порога 25% заключается в том, что после завершения очистки Scavenge пространство To станет пространством From, и следующее выделение памяти будет выполняться в этом пространстве. Если соотношение слишком велико, это повлияет на последующее выделение памяти.
старое поколение
1. Введение
В старом поколении уцелевшие объекты составляют большую долю.Если алгоритм Scavenge продолжит использоваться для управления, возникнут две проблемы:
- Из-за большого количества уцелевших объектов эффективность копирования уцелевших объектов будет очень низкой.
- Использование алгоритма Scavenge приведет к потере половины памяти.Поскольку старое поколение занимает гораздо больше памяти кучи, чем новое поколение, потери будут очень серьезными.
Поэтому V8 в основном используется в старом поколенииMark-Sweepа такжеMark-SweepВ сочетании с вывозом мусора.
2. Mark-Sweep
Mark-Sweep означает Mark Sweep, который делится на два этапа: Mark и Sweep.
В отличие от Scavenge, Mark-Sweep не разделяет память на две части, поэтому не происходит потери половины пространства. Mark-Sweep просматривает все объекты в динамической памяти на этапе маркировки и помечает живые объекты, а на последующем этапе очистки очищает только те объекты, которые не отмечены.
То есть Scavenge копирует только живые объекты, а Mark-Sweep удаляет только мертвые объекты. Живые объекты занимают лишь небольшую часть в молодом поколении, а мертвые объекты занимают лишь небольшую часть в старом поколении, поэтому оба метода переработки могут быть эффективно обработаны.
Давайте посмотрим на блок-схему:
step1.В старом поколении есть объекты A,B,C,D,E,F
step2, GC переходит к фазе маркировки, помечая A, C и E как выжившие объекты.
step3Сборщик мусора переходит к фазе очистки и освобождает место в памяти, занятое мертвыми объектами B, D и F.
Видно, что самая большая проблема Mark-Sweep заключается в том, что после очистки и повторного использования пространство памяти будет казаться прерывистым. Эта фрагментация памяти может вызвать проблемы при последующем выделении памяти.
Если есть необходимость выделить большой объем памяти, потому что оставшегося фрагментированного пространства недостаточно для завершения выделения, сборка мусора будет запущена заранее, и эта сборка не нужна.
2. Mark-Compact
Для решения проблемы фрагментации памяти Mark-Sweep был предложен Mark-Compact.
**Mark-Compact означает отделку маркировки, ** разработан на основе Mark-Sweep. После того, как Mark-Compact пометит уцелевшие объекты, он переместит уцелевшие объекты в один конец пространства памяти.После завершения перемещения вся память за границей будет очищена напрямую. Как показано ниже:
step1, Есть объекты A, B, C, D, E, F в старом поколении (такое же как Mark-Sweep)
step2GC переходит к фазе маркировки, помечая A, C и E как выжившие объекты (так же, как Mark-Sweep).
step3GC переходит в фазу сортировки, перемещая все уцелевшие объекты в одну сторону пространства памяти, серая часть — это место, освободившееся после перемещения
step4, Сборщик мусора входит в фазу очистки и одновременно освобождает всю память по другую сторону границы.
3. Сочетание двух
В стратегии переработки V8 используются комбинации Mark-Sweep и Mark-Conpact.
Поскольку Mark-Conpact должен перемещать объекты, скорость его выполнения не может быть очень высокой.Что касается компромиссов, V8 в основном использует Mark-Sweep и использует Mark-Sweep только тогда, когда недостаточно места для размещения объектов, перемещенных из нового поколения Компактный.
Суммировать
Механизм сборки мусора V8 разделен на новое поколение и старое поколение.
Новое поколение в основном управляется Scavenge.Основной реализацией является алгоритм Чейни, который делит память на два блока.Используемое пространство называется From, а свободное пространство называется To.Новые объекты сначала выделяются в пространство From, и выживет, когда пространство почти заполнено.Объект копируется в пространство «Кому», а затем пространство памяти «Из» очищается.В это время пространство «От» и «Кому» меняются местами, и выделение памяти продолжается.Когда эти два условия выполнены, объект будет переведен из нового поколения в старое поколение.
В старом поколении в основном используются алгоритмы Mark-Sweep и Mark-Compact, один из которых — удаление меток, а другой — сортировка меток. Разница между ними заключается в том, что Mark-Sweep будет генерировать фрагментированную память после сборки мусора, в то время как Mark-Compact выполнит один шаг сортировки перед очисткой, переместив уцелевшие объекты в одну сторону, а затем очистив память на другой стороне памяти. граница, чтобы она была свободна.Память непрерывная, но проблема в том, что скорость будет медленнее. В V8 старое поколение управляется как Mark-Sweep, так и Mark-Compact.
Вышеизложенное является полным содержанием этой статьи.В процессе написания я ссылался на многие китайские и зарубежные статьи.Справочники включают «Введение в NodeJS» и «Продвинутое программирование на JavaScript» Пак Да. Мы не обсуждали здесь конкретную реализацию алгоритма, и заинтересованные друзья могут продолжить его углубленное изучение.
Наконец, спасибо, что смогли прочитать это.Если в тексте есть неясное или неправильное место, пожалуйста, оставьте мне сообщение~~
Добро пожаловать, чтобы обратить внимание на мой общедоступный номер
Ссылка на ссылку
medium.com/@_Nevertheless/gar… alinode.aliyun.com/blog/14 Вууху. Руань Ифэн.com/blog/2017/0… сегмент fault.com/ah/119000000…