Чем больше вы знаете, тем больше вы не знаете
点赞
Посмотри еще раз, аромат остался в руке, и слава
предисловие
Языки низкого уровня, такие как C, обычно имеют низкоуровневые интерфейсы управления памятью, такие как malloc() и free() для выделения и освобождения памяти. Для JavaScript память выделяется при создании переменных (объектов, строк и т. д.) и "автоматически" освобождается, когда они больше не используются. Этот процесс автоматического освобождения памяти называется сборкой мусора. Из-за существования механизма автоматической сборки мусора большинство разработчиков Javascript считают, что они не могут заботиться об управлении памятью, поэтому в некоторых случаях это приведет к утечкам памяти.
жизненный цикл памяти
Память, выделенная в среде JS, имеет следующий цикл объявления:
- Выделение памяти: когда мы объявляем переменные, функции и объекты, система автоматически выделяет для них память.
- Использование памяти: то есть чтение и запись памяти, то есть использование переменных, функций и т. д.
- Утилизация памяти: после использования память, которая больше не используется, автоматически перерабатывается механизмом сборки мусора.
Распределение памяти JS
Чтобы не заставлять программиста выделять память, 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);
Некоторые вызовы функций приводят к выделению памяти объекта:
var d = new Date(); // 分配一个 Date 对象
var e = document.createElement('div'); // 分配一个 DOM 元素
Некоторые методы выделяют новые переменные или новые объекты:
var s = "azerty";
var s2 = s.substr(0, 3); // s2 是一个新的字符串
// 因为字符串是不变量,
// JavaScript 可能决定不分配内存,
// 只是存储了 [0-3] 的范围。
var a = ["ouais ouais", "nan nan"];
var a2 = ["generation", "nan nan"];
var a3 = a.concat(a2);
// 新数组有四个元素,是 a 连接 a2 的结果
Использование памяти JS
Процесс использования значения на самом деле является операцией чтения и записи в выделенную память. Чтение и запись могут представлять собой запись переменной или значения свойства объекта или даже передачу параметров функции.
var a = 10; // 分配内存
console.log(a); // 对内存的使用
Восстановление памяти JS
В JS есть механизм автоматической сборки мусора, так каков же принцип этого механизма автоматической сборки мусора? На самом деле это очень просто, то есть найти те значения, которые уже не используются, а затем освободить занимаемую ими память.
Большинство проблем с управлением памятью находятся на этом этапе. Самая сложная задача здесь — найти переменные, которые больше не нужны.
Больше не нужно использовать переменную, которая является концом жизненного цикла переменных, локальные переменные, локальные переменные существуют только во время выполнения функции, Когда функция завершается, других ссылок (замыканий) нет, тогда переменная помечается как восстановление.
Жизненный цикл глобальных переменных не закончится, пока браузер не выгрузит страницу, а это значит, что глобальные переменные не будут удалены сборщиком мусора.
Из-за наличия механизма автоматической сборки мусора разработчики могут не заботиться или не обращать внимания на связанные с этим вопросы освобождения памяти, но освобождение бесполезной памяти объективно. К сожалению, даже без учета влияния сборки мусора на производительность современные современные алгоритмы сборки мусора не могут разумно собирать все крайние случаи.
Далее давайте изучим механизм сборки мусора JS.
вывоз мусора
Цитировать
Алгоритмы сборки мусора в основном полагаются на концепцию ссылок.
В контексте управления памятью говорят, что объект ссылается на другой объект, если у него есть разрешение (неявное или явное) на доступ к другому объекту.
Например, объект Javascript имеет ссылки на свой прототип (неявные ссылки) и ссылки на свои свойства (явные ссылки).
Здесь понятие «объект» относится не только к объектам JavaScript, но и к области видимости функции (или глобальной лексической области видимости).
сборка мусора с подсчетом ссылок
Это самый простой алгоритм сборки мусора.
Алгоритм подсчета ссылок определяет «память больше не используется» как простой критерий, чтобы увидеть, есть ли у объекта ссылка на нее. Если никакие другие объекты не указывают на него, объект больше не нужен.
var o = {
a: {
b:2
}
};
// 两个对象被创建,一个作为另一个的属性被引用,另一个被分配给变量o
// 很显然,没有一个可以被垃圾收集
var o2 = o; // o2变量是第二个对“这个对象”的引用
o = 1; // 现在,“这个对象”的原始引用o被o2替换了
var oa = o2.a; // 引用“这个对象”的a属性
// 现在,“这个对象”有两个引用了,一个是o2,一个是oa
o2 = "yo"; // 最初的对象现在已经是零引用了
// 他可以被垃圾回收了
// 然而它的属性a的对象还在被oa引用,所以还不能回收
oa = null; // a属性的那个对象现在也是零引用了
// 它可以被垃圾回收了
Как видно из вышеизложенного, алгоритм подсчета ссылок является простым и эффективным алгоритмом. Но у него есть фатальная проблема: циклические ссылки.
Если два объекта ссылаются друг на друга, даже если они больше не используются, сборка мусора не будет выполняться, что приведет к утечке памяти.
Вот пример циклической ссылки:
function f(){
var o = {};
var o2 = {};
o.a = o2; // o 引用 o2
o2.a = o; // o2 引用 o 这里
return "azerty";
}
f();
Выше мы объявили функцию f, содержащую два объекта, которые ссылаются друг на друга. После завершения вызывающей функции объекты o1 и o2 фактически покинули область действия функции и, следовательно, больше не нужны. Однако по принципу подсчета ссылок взаимные ссылки между ними все равно существуют, поэтому эта часть памяти не будет переработана, и утечки памяти неизбежны.
Давайте рассмотрим практический пример:
var div = document.createElement("div");
div.onclick = function() {
console.log("click");
};
Вышеупомянутый метод написания JS очень распространен, создавая элемент DOM и привязывая событие клика. На данный момент переменная div имеет ссылку на обработчик события, а обработчик события также имеет ссылку на div! (переменные div доступны внутри функции). Появляется последовательная ссылка, и по описанному выше алгоритму неизбежно происходит утечка этой части памяти.
Чтобы решить проблему, вызванную циклическими ссылками, современные браузеры реализуют сборку мусора с использованием алгоритма пометки-очистки.
Алгоритм развертки пометки
Алгоритм пометки-очистки определяет «неиспользуемые объекты» как «недостижимые объекты». Проще говоря, это регулярное сканирование объектов в памяти из корня (глобальный объект в JS). Все объекты, к которым можно получить доступ из корня, по-прежнему необходимо использовать. Объекты, недоступные из корня, помечаются как неиспользуемые и будут переработаны позже.
Как видно из этого понятия, недостижимые объекты включают в себя понятие объектов без ссылок (объекты без каких-либо ссылок также являются недостижимыми объектами). Но обратное не обязательно верно.
процесс работы:
- Сборщик мусора пометит все переменные, хранящиеся в памяти, во время выполнения.
- Начинает с корня и очищает разметку объектов, до которых можно добраться.
- Те переменные, у которых еще есть маркеры, считаются готовыми к удалению.
- Наконец, сборщик мусора выполнит последний шаг очистки памяти, уничтожив отмеченные значения и освободив место в памяти, которое они занимают.
Циркулярные ссылки больше не проблема
Посмотрите на предыдущий пример циклической ссылки:
function f(){
var o = {};
var o2 = {};
o.a = o2; // o 引用 o2
o2.a = o; // o2 引用 o
return "azerty";
}
f();
После возврата вызова функции два объекта с циклическими ссылками больше не могут получать свои ссылки от глобального объекта во время сборки мусора. Следовательно, они будут собраны сборщиком мусора.
утечка памяти
что такое утечка памяти
Программе нужна память для работы. Операционная система или среда выполнения (runtime) должны предоставлять память всякий раз, когда программа запрашивает ее.
Для непрерывно работающих служебных процессов (демонов) память, которая больше не используется, должна быть вовремя освобождена. В противном случае использование памяти будет становиться все выше и выше, что повлияет на производительность системы на низком уровне и в худшем случае приведет к сбою процесса.
По сути, утечка памяти — это программа, которая не может освободить память, которая больше не используется из-за небрежности или ошибки, что приводит к пустой трате памяти.
Как выявить утечки памяти
Эмпирическое правило заключается в том, что если после пяти последовательных сборок мусора использование памяти больше, чем в первый раз, возникает утечка памяти. Для этого требуется просмотр использования памяти в режиме реального времени.
В браузере Chrome мы можем просмотреть использование памяти следующим образом.
- Откройте инструменты разработчика и выберите панель «Производительность».
- Отметьте Память вверху
- Нажмите кнопку записи в верхнем левом углу
- Выполнять различные операции на странице, имитируя использование пользователем
- Через некоторое время нажмите кнопку остановки диалогового окна, и на панели отобразится использование памяти за этот период.
Взгляните на рендеринг:
У нас есть два способа определить, есть ли в данный момент утечка памяти:
- После нескольких снимков сравните использование памяти в каждом снимке.Если есть восходящая тенденция, можно считать, что есть утечка памяти
- После снимка посмотрите на текущий график тренда использования памяти, если тренд нестабилен и показывает восходящий тренд, можно считать, что имеет место утечка памяти.
Используйте метод process.memoryUsage, предоставленный Node, для просмотра ситуации с памятью в среде сервера.
console.log(process.memoryUsage());
// {
// rss: 27709440,
// heapTotal: 5685248,
// heapUsed: 3449392,
// external: 8772
// }
process.memoryUsage возвращает объект, содержащий информацию об использовании памяти процессом Node.
Объект содержит четыре поля, единица измерения — байты, значения следующие:
- rss (размер резидентного набора): Использование всей памяти, включая область инструкций и стек.
- heapTotal: память, занимаемая «кучей», включая используемую и неиспользуемую.
- heapUsed: Используемая часть кучи.
- external: память, занимаемая объектами C++ внутри движка V8.
Для определения утечек памяти поле heapUsed должно превалировать.
Распространенные случаи утечки памяти
неожиданная глобальная переменная
function foo() {
bar1 = 'some text'; // 没有声明变量 实际上是全局变量 => window.bar1
this.bar2 = 'some text' // 全局变量 => window.bar2
}
foo();
В этом примере случайно созданы две глобальные переменные bar1 и bar2.
Забытые таймеры и обратные вызовы
Во многих библиотеках, если используется шаблон наблюдателя, предусмотрены методы обратного вызова для вызова некоторых функций обратного вызова. Не забудьте переработать эти функции обратного вызова. Возьмем пример setInterval:
var serverData = loadData();
setInterval(function() {
var renderer = document.getElementById('renderer');
if(renderer) {
renderer.innerHTML = JSON.stringify(serverData);
}
}, 5000); // 每 5 秒调用一次
Если последующие элементы рендерера удаляются, весь таймер фактически не действует. Но если вы не перезапустите таймер, весь таймер все еще действителен, не только таймер не может быть восстановлен памятью, Зависимости в функциях таймера также не могут быть переработаны. Серверные данные в этом случае также не могут быть переработаны.
Закрытие
В разработке JS мы часто будем использовать замыкания, внутреннюю функцию, которая имеет доступ к переменным внешней функции, которая ее содержит. Замыкания также могут вызвать утечку памяти в следующих случаях:
var theThing = null;
var replaceThing = function () {
var originalThing = theThing;
var unused = function () {
if (originalThing) // 对于 'originalThing'的引用
console.log("hi");
};
theThing = {
longStr: new Array(1000000).join('*'),
someMethod: function () {
console.log("message");
}
};
};
setInterval(replaceThing, 1000);
В этом коде каждый раз, когда вызывается replaceThing, theThing получает огромный массив и объект для нового замыкания someMethod. Тем временем неиспользуемым является замыкание, которое ссылается на originalThing.
Ключом к этой парадигме является то, что области действия являются общими для замыканий. Когда этот код выполняется повторно, память продолжает расти.
Дом справочника
Много раз наша операция с DOM будет сохранять ссылку на DOM в массиве или MAP.
var elements = {
image: document.getElementById('image')
};
function doStuff() {
elements.image.src = 'http://example.com/image_name.png';
}
function removeImage() {
document.body.removeChild(document.getElementById('image'));
// 这个时候我们对于 #image 仍然有一个引用, Image 元素, 仍然无法被内存回收.
}
В приведенном выше случае, даже если мы удалим элемент изображения, все еще будет ссылка на элемент изображения, который все еще не может быть выровнен для освобождения памяти.
Еще один момент, на который следует обратить внимание, — это ссылка на конечный узел дерева Dom. Например: если мы ссылаемся на элемент td в таблице, как только вся таблица будет удалена из Dom, мы интуитивно чувствуем, что восстановление памяти должно перерабатывать другие элементы, кроме упомянутого td. Но на самом деле элемент td является дочерним элементом всей таблицы и сохраняет ссылку на своего родителя. Это приведет к отсутствию высвобождения памяти для всей таблицы. Поэтому мы должны быть осторожны со ссылками на элементы Dom.
Как избежать утечек памяти
Помните принцип: что вам не нужно, вовремя верните.
- Сократите ненужные глобальные переменные и используйте строгий режим, чтобы избежать случайного создания глобальных переменных.
- После того, как вы закончите использовать данные, разыменуйте (переменные в замыканиях, ссылки на дом, очистку таймера) во времени.
- Организуйте свою логику, чтобы избежать таких проблем, как бесконечные циклы, которые вызывают зависание и сбои браузера.
Ссылаться на
- MDN — Управление памятью
- продвинутое программирование на JavaScript
- Полное руководство по JavaScript
- Учебное пособие по утечке памяти в JavaScript
- Интересная утечка памяти в JavaScript
Рекомендуемая серия статей
- Анализ и реализация одностраничной маршрутизации «переднего плана»
- «Front-end Advanced» полностью понимает каррирование функций.
- Память кучи памяти стека "Front-end advanced" в JS
- Массив "Front-end advanced" вышел из строя
напиши в конце
- Если в статье есть ошибки, исправьте их в комментариях, если статья вам поможет, добро пожаловать
点赞
а также关注
- Эта статья была впервые опубликована одновременно сgithub, доступны наgithubНайдите больше отличных статей в
Watch
&Star ★
- Для последующих статей см.:строить планы
Добро пожаловать в публичный аккаунт WeChat
【前端小黑屋】
, 1–3 высококачественные высококачественные статьи публикуются каждую неделю, чтобы помочь вам в продвижении вперед.