Механизм памяти JavaScript (требуется для продвинутых студентов, изучающих интерфейс)

внешний интерфейс алгоритм JavaScript V8

Введение

Каждый язык программирования имеет свой механизм управления памятью, например, простой C имеет низкоуровневые примитивы управления памятью, такие какmalloc(),free(). Точно так же, когда мы изучаем JavaScript, необходимо понимать механизм управления памятью JavaScript. Механизм управления памятью JavaScript заключается в том, что примитивы памяти выделяются при создании переменных (объектов, строк и т. д.), а затем «автоматически» освобождаются, когда они больше не используются. Последнее называется сборкой мусора. Эта «автоматика» сбивает с толку и дает разработчикам JavaScript (и других языков высокого уровня) иллюзию того, что они могут забыть об управлении памятью. Для фронтенд-разработки пространство памяти — это понятие, которое не часто упоминается, и его легко игнорировать. Конечно, это включает меня. Долгое время считалось, что концепция пространства памяти не так важна при изучении JS. Однако, когда я вернулся и переделал основы JS, я обнаружил, что из-за моего смутного понимания их я не понимал многих вещей. Например, как насчет самых основных справочных типов данных и передачи по ссылке? Например, в чем разница между поверхностной копией и глубокой копией? Есть также замыкания, прототипы и многое другое. Но на самом деле в процессе разработки с помощью JavaScript понимание механизма памяти JavaScript помогает разработчикам четко понимать, что происходило во время выполнения написанного ими кода, а также может улучшить качество кода проекта.

модель памяти

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

Основные типы данных и стековая память

Базовые типы данных в JS, эти значения имеют фиксированный размер, часто хранятся в памяти стека (кроме замыканий), а место для хранения выделяется системой автоматически. Мы можем напрямую манипулировать значениями, хранящимися в пространстве памяти стека, поэтому доступ к базовым типам данных осуществляется по значению Хранение и использование данных в памяти стека аналогично структуре данных стека в структуре данных, следуя принципу «последний пришел — первый ушел». Основной тип данных:Number String Null Undefined BooleanНапомним, что этот вопрос часто задают в интервью, но многие люди не могут на него ответить~~ Чтобы просто понять, как хранится пространство стековой памяти, мы можем проанализировать его по аналогии с коробкой для пинг-понга.

коробка для настольного тенниса
5
4
3
2
1

Этот шарик для пинг-понга хранится точно так же, как данные хранятся в стеке. Мяч для пинг-понга 5 в верхней части коробки нужно положить последним, но его можно использовать первым. И если мы хотим использовать нижний шарик для пинг-понга 1, мы должны вынуть 4 верхних шарика для пинг-понга, чтобы шарик для пинг-понга 1 оказался наверху коробки. Это характеристика пространства стека FIFO и LIFO.

Справочные типы данных и динамическая память

В отличие от других языков, эталонные типы данных JS, такие как массивы, не имеют фиксированного размера. Значения ссылочных типов данных — это объекты, которые хранятся в куче памяти. JS не допускает прямого доступа к местам в куче памяти, поэтому мы не можем напрямую манипулировать пространством кучи памяти объекта. Манипулируя объектом, вы на самом деле манипулируете ссылкой на объект, а не на сам объект. Следовательно, все значения ссылочных типов доступны по ссылке. Ссылку здесь можно приблизительно понимать как адрес, хранящийся в памяти стека, который связан с фактическим значением памяти кучи. Способ доступа к данным в куче очень похож на книжные полки и книги. Хотя книги также хранятся на книжной полке в упорядоченном порядке, пока мы знаем название книги, мы можем легко достать нужную книгу, без необходимости убирать с нее все мячи для настольного тенниса, как при взятии стола. теннисный мяч из коробки для настольного тенниса.Выньте его, чтобы получить мяч для настольного тенниса в середине. Например, в данных формата JSON хранимая нами пара «ключ-значение» может быть не в порядке, потому что разница в порядке не влияет на наше использование, нам нужно только позаботиться о названии книги.

Чтобы лучше понять память стека и память кучи, мы можем объединить следующие примеры и диаграммы для понимания.
var a1 = 0; // 栈
var a2 = 'this is string'; // 栈
var a3 = null; // 栈
var b = { m: 20 }; // 变量b存在于栈中,{m: 20} 作为对象存在于堆内存中
var c = [1, 2, 3]; // 变量c存在于栈中,[1, 2, 3] 作为对象存在于堆内存中

имя переменной конкретное значение
c 0x0012ff7d
b 0x0012ff7c
a3 null
a2 this is string
a1 0

[пространство памяти стека] ------->

        堆内存空间
        [1,2,3]           
                    {m:20}           

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

// demo01.js
var a = 20;
var b = a;
b = 30;
// 这时a的值是多少?

// demo02.js
var m = { a: 10, b: 20 };
var n = m;
n.a = 15;
// 这时m.a的值是多少

Когда данные в памяти стека копируются, система автоматически назначает новое значение новой переменной.var b = aПосле казни,aиbХотя значения равны 20, на самом деле они не зависят друг от друга. Конкретно как показано. Поэтому после того, как мы изменим значение b, значение a не изменится.

объем памяти стека
a 20

[перед копированием]

объем памяти стека
b 20
a 20

[после копирования]

объем памяти стека
b 30
a 20

[bзначение после модификации]
Это схема demo1

В demo02 мы проходимvar n = mВыполните операцию, которая копирует ссылочный тип. Копия ссылочного типа также автоматически выделяет новое значение для новой переменной и сохраняет его в памяти стека, но отличие состоит в том, что это новое значение является просто адресным указателем ссылочного типа. Когда указатели адресов одинаковы, хотя они и независимы друг от друга, конкретный объект, к которому обращаются в памяти кучи, фактически один и тот же. |Пространство памяти стека|| |имя переменной|конкретное значение|

m 0x0012ff7d

[перед копированием]

куча памяти
{a:10,b:20}

[перед копированием]

объем памяти стека
имя переменной
m
n

[после копирования]

куча памяти
{a:10,b:20}

[после копирования]

Это иллюстрация demo2

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

жизненный цикл памяти

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

  1. Выделение памяти: когда мы объявляем переменные, функции и объекты, система автоматически выделяет для них память.
  2. Использование памяти: то есть чтение и запись памяти, то есть использование переменных, функций и т. д.
  3. Утилизация памяти: после использования память, которая больше не используется, автоматически перерабатывается механизмом сборки мусора.

Для простоты понимания мы используем простой пример для объяснения этого цикла.

var a = 20;  // 在内存中给数值变量分配空间
alert(a + 100);  // 使用内存
var a = null; // 使用完毕之后,释放内存空间

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

Теперь подумайте об этом, с точки зрения памятиnullиundefinedВ чем принципиальная разница?

Почемуtypeof(null) //object typeof(undefined) //undefined?

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

правильный,ES6СинтаксисconstОбъявите константу только для чтения. После объявления значение константы не может быть изменено. Но следующий код можно изменитьconstценность, почему?

const foo = {}; 
foo.prop = 123;
foo.prop // 123
foo = {}; // TypeError: "foo" is read-only

восстановление памяти

В JavaScript есть механизм автоматической сборки мусора, так каков же принцип этого механизма автоматической сборки мусора? На самом деле это очень просто, то есть найти те значения, которые уже не используются, а затем освободить занимаемую ими память. Сборщик мусора будет выполнять свободную операцию через равные промежутки времени. В JavaScript наиболее часто используемый алгоритм состоит в том, чтобы найти, какие объекты больше не используются алгоритмом пометки-очистки, поэтомуa = nullНа самом деле, он просто выполняет операцию ссылки на выпуск, пустьaИсходное соответствующее значение теряет ссылку и покидает среду выполнения.Это значение будет найдено и освобождено при следующем выполнении сборщиком мусора операции. И разыменование в нужное время — важный способ повысить производительность страницы.

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

  • Взяв в качестве примера движок Google V8, все объекты JAVASCRIPT в движке V8 размещаются в куче. Когда мы объявляем и присваиваем переменную в коде, движок V8 выделяет под эту переменную часть памяти кучи. Если запрошенной памяти недостаточно для хранения этой переменной, движок V8 будет продолжать обращаться за памятью до тех пор, пока размер кучи не достигнет верхнего предела памяти движка V8 (по умолчанию верхний предел размера кучи памяти Движок V8 в 64-разрядных системах (1464 МБ или 732 МБ в 32-разрядной системе).

  • Кроме того, механизм V8 выполняет управление поколениями объектов JAVASCRIPT в куче памяти. Новое поколение: Новое поколение — это объект JAVASCRIPT с коротким жизненным циклом, например, временные переменные, строки и т. д.; Старое поколение. Под старым поколением понимаются объекты, которые выживают после нескольких сборок мусора и имеют длительный жизненный цикл, например объекты главного контроллера и сервера.

Пожалуйста, смотрите следующий код, старое железо, для анализа сборки мусора.

function fun1() {
    var obj = {name: 'csa', age: 24};
}
 
function fun2() {
    var obj = {name: 'coder', age: 2}
    return obj;
}
 
var f1 = fun1();
var f2 = fun2();

В приведенном выше коде при выполненииvar f1 = fun1();, среда выполнения создает{name:'csa', age:24}Этот объект при выполненииvar f2 = fun2();, среда выполнения создает{name:'coder', age=2}Затем этот объект освобождается, когда приходит следующая сборка мусора.{name:'csa', age:24}Память этого объекта не освобождается{name:'coder', age:2}памяти для этого объекта. Это потому, что вfun2()генерал-лейтенант{name:'coder, age:2'}Этот объект возвращается, и его ссылка присваиваетсяf2переменная, а потомуf2Этот объект является глобальной переменной, поэтому, когда страница не выгружается,f2объект указывал на{name:'coder', age:2}не будет переработан. Из-за специфики языка JavaScript (замыкания...) крайне сложно определить, будет ли объект переработан, просто взгляните на него.

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

Основная идея алгоритма сборки мусора заключается в том, как определить, что память больше не используется.

алгоритм подсчета ссылок

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

Алгоритм подсчета ссылок определяет «память больше не используется» как простой критерий, чтобы увидеть, есть ли у объекта ссылка на нее. Если никакие другие объекты не указывают на него, объект больше не нужен.

Давайте рассмотрим пример, старые утюги:

// 创建一个对象person,他有两个指向属性age和name的引用
var person = {
    age: 12,
    name: 'aaaa'
};

person.name = null; // 虽然设置为null,但因为person对象还有指向name的引用,因此name不会回收

var p = person; 
person = 1;         //原来的person对象被赋值为1,但因为有新引用p指向原person对象,因此它不会被回收

p = null;           //原person对象已经没有引用,很快会被回收

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

Давайте посмотрим на другой пример, старые утюги:

function cycle() {
    var o1 = {};
    var o2 = {};
    o1.a = o2;
    o2.a = o1; 

    return "Cycle reference!"
}

cycle();

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

var div = document.createElement("div");
div.onclick = function() {
    console.log("click");
};

Вышеупомянутый метод написания JS очень распространен, создавая элемент DOM и привязывая событие щелчка. Так в чем же проблема? Обратите внимание, что переменная div имеет ссылку на обработчик события, а обработчик события также имеет ссылку на div! (переменные div доступны внутри функции). Появляется последовательная ссылка, согласно упомянутому выше алгоритму, эта часть памяти неизбежно будет утекать. Теперь вы понимаете, почему фронтенд-программисты ненавидят IE? IE, в котором много багов и который до сих пор занимает большой рынок, — враг фронтенд-разработки на всю жизнь! Моя дорогая, не бывает убийства без купли-продажи.

Алгоритм развертки пометки

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

Алгоритм пометки-очистки определяет «неиспользуемые объекты» как «недостижимые объекты». Проще говоря, это регулярное сканирование объектов в памяти из корня (глобальный объект в JS). Все объекты, к которым можно получить доступ из корня, по-прежнему необходимо использовать. Объекты, недоступные из корня, помечаются как неиспользуемые и будут переработаны позже.

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

В соответствии с этой концепцией приведенный выше пример может быть правильно собран сборщиком мусора (уважаемый, подумайте, почему?).

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

Как написать JS-код, удобный для управления памятью?

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

Единственное, на что следует обратить внимание в современных браузерах, — это явно отделять объекты, которые необходимо переработать, из корня. Иногда эта связь неочевидна, и из-за надежности алгоритма подметания меток эта проблема возникает реже. Наиболее распространенные утечки памяти обычно связаны с привязками элементов DOM:

email.message = document.createElement(“div”);
displayList.appendChild(email.message);

// 稍后从displayList中清除DOM元素
displayList.removeAllChildren();

Элемент div был удален из дерева DOM, что означает, что элемент div недоступен из корня дерева DOM. Обратите внимание, однако, что элемент div также привязан к объекту электронной почты. Таким образом, пока объект электронной почты существует, элемент div будет оставаться в памяти.

резюме

Если ваши ссылки содержат только несколько JS-взаимодействий, то управление памятью не будет вас сильно беспокоить. Как только вы начнете создавать SPA среднего и крупного масштаба или серверные и настольные приложения, утечка памяти должна быть на повестке дня. Не довольствуйтесь написанием работающих программ и не думайте, что модернизация машины все решит.

утечка памяти

Что такое утечка памяти

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

char * buffer;
buffer = (char*) malloc(42);

// Do something with buffer

free(buffer);

Неважно, если вы не понимаете, выше приведен код языка C,mallocСпособ применяется для запоминания.После применения его необходимо использовать самостоятельно.freeметод освобождения памяти. Это громоздко, поэтому в большинстве языков предусмотрено автоматическое управление памятью для снижения нагрузки на программиста.Это называется «сборщик мусора», о котором уже упоминалось и дальше обсуждаться не будет.

Как выявить утечки памяти

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

метод браузера

  1. Откройте инструменты разработчика и выберите панель «Таймлайн».
  2. Проверьте память в поле «Захват» вверху.
  3. Нажмите кнопку записи в верхнем левом углу.
  4. Выполняйте различные операции на странице, чтобы имитировать использование пользователем.
  5. Через некоторое время нажмите кнопку остановки диалогового окна, и на панели отобразится использование памяти за этот период.

Если использование памяти в основном стабильно и близко к уровню, значит утечки памяти нет. В противном случае это утечка памяти.

метод командной строки

командная строка может использоватьNodeкоторый предоставилprocess.memoryUsageметод.

console.log(process.memoryUsage());
// { rss: 27709440,
//  heapTotal: 5685248,
//  heapUsed: 3449392,
//  external: 8772 }

process.memoryUsageВозвращает объект, содержащийNodeИнформация об использовании памяти процессом. Объект содержит четыре поля, единица измерения — байт, смысл следующий.

Resident Set(常驻内存)
Code Segment(代码区)
Stack(Local Variables, Pointers)
Heap(Objects, Closures)
Used Heap
  • rss(resident set size): Использование всей памяти, включая область инструкций и стек.
  • heapTotal: Память, занятая "кучей", включая используемую и неиспользуемую.
  • heapUsed: Используемая часть кучи.
  • external: Память, занятая объектами C++ внутри движка V8.

Чтобы определить утечку памяти,heapUsedполе должно превалировать.

WeakMap

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

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

ES6 учитывает это и вводит две новые структуры данных:WeakSetиWeakMap. Их ссылки на значения не включаются в механизм сборки мусора, поэтому в названии будет «Слабая», указывающая на то, что это слабая ссылка.

Ниже сWeakMapНапример, посмотрите, как он решает проблему утечки памяти.

const wm = new WeakMap();

const element = document.getElementById('example');

wm.set(element, 'some information');
wm.get(element) // "some information"

В приведенном выше коде сначала создайте новыйWeakmapпример. Затем сохраните узел DOM в качестве имени ключа в экземпляре и сохраните некоторую дополнительную информацию в качестве значения ключа вWeakMapв. В настоящее время,WeakMapСсылка на элемент в нем является слабой ссылкой и не будет учитываться в механизме сборки мусора.

То есть счетчик ссылок объекта узла DOM равен 1, а не 2. В это время, как только ссылка на узел будет устранена, занимаемая им память будет освобождена механизмом сборки мусора.WeakmapСохраненная пара ключ-значение также автоматически исчезнет.

По сути, если вы хотите добавить данные в объект, не мешая механизму сборки мусора, вы можете использоватьWeakMap.

Пример WeakMap

WeakMapПример трудно продемонстрировать, потому что ссылки внутри него не исчезают автоматически. В этот момент все остальные ссылки освобождаются, и нет ссылок наWeakMapИмя ключа было изменено, поэтому невозможно проверить, существует ли имя ключа. (Подробности см. в статье об утечке памяти, написанной г-ном Руаном Ифэном). над.

Особая благодарность:

Наконец, я сожалею, что продвигаю свою работу на основеTaroБиблиотека компонентов, написанная фреймворком:MP-ColorUI.

Я очень рад, что могу сыграть главную роль, спасибо.

Щелкните здесь для документации

Нажмите здесь, чтобы узнать адрес GitHub