Сборка мусора и утечки памяти в JavaScript

JavaScript

предисловие

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

В таких языках, как C и C++, разработчики могут напрямую управлять приложением и восстановлением памяти. Но в языках Java, C# и JavaScript применение и освобождение переменного пространства памяти обрабатывается самой программой, и разработчикам не нужно об этом заботиться. То есть в Javascript есть механизм автоматической сборки мусора (Garbage Collecation).

Чтобы прочитать больше качественных статей, нажмитеБлог GitHub, Вас ждут пятьдесят качественных статей в год!

Необходимость вывоза мусора

Следующий отрывок цитируется из Полного руководства по JavaScript (4-е издание).

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

Этот отрывок объясняет, зачем системе нужна сборка мусора.В отличие от C/C++, в JavaScript есть собственный механизм сборки мусора.

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

var a = "浪里行舟";
var b = "前端工匠";
var a = b; //重写a

После выполнения этого кода строка "boating in the waves" теряет свою ссылку (ранее на нее ссылался a). После того, как система обнаружит этот факт, она освободит место для хранения строки, чтобы ее можно было использовать повторно.

2. Механизм сбора мусора

Как механизм сборки мусора узнает, какая память больше не нужна?

Существует два метода сбора мусора:Очистка меток, подсчет ссылок. Подсчет ссылок встречается реже, а подметание меток — более распространено.

1. Отметить как очищенный

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

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

Давайте используем пример, чтобы объяснить этот метод:

var m = 0,n = 19 // 把 m,n,add() 标记为进入环境。
add(m, n) // 把 a, b, c标记为进入环境。
console.log(n) // a,b,c标记为离开环境,等待垃圾回收。
function add(a, b) {
  a++
  var c = a + b
  return c
}

2. Подсчет ссылок

Так называемый «счетчик ссылок» означает, что языковой движок имеет «справочную таблицу», которая сохраняет в памяти время обращения всех ресурсов (обычно различных значений). Если количество ссылок на значение равно 0, это означает, что значение больше не используется, поэтому память может быть освобождена.

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

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

var arr = [1, 2, 3, 4];
arr = [2, 4, 5]
console.log('浪里行舟');

В приведенном выше коде массив [1, 2, 3, 4] — это значение, занимающее память. Переменная arr является единственной ссылкой на это значение, поэтому счетчик ссылок равен 1. Хотя arr не используется в следующем коде, он продолжает занимать память. Что касается того, как освободить память, мы представим это ниже.

В третьей строке кода переменная arr, на которую ссылается массив [1, 2, 3, 4], получила другое значение, тогда времена обращения к массиву [1, 2, 3, 4] уменьшаются на 1, и в это время эталонное время Если оно становится равным 0, это означает, что нет возможности получить доступ к этому значению, поэтому пространство памяти, занимаемое им, может быть восстановлено.

Но у подсчета ссылок есть самая большая проблема: циклические ссылки.

function func() {
    let obj1 = {};
    let obj2 = {};

    obj1.a = obj2; // obj1 引用 obj2
    obj2.a = obj1; // obj2 引用 obj1
}

Когда функция func завершает выполнение, возвращаемое значение не определено, поэтому вся функция и внутренние переменные должны быть переработаны, но в соответствии с методом подсчета ссылок опорные времена obj1 и obj2 не равны 0, поэтому они не будут переработаны.

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

obj1 = null;
obj2 = null;

3. Какие ситуации могут вызвать утечку памяти?

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

1. Неожиданная глобальная переменная

function foo(arg) {
    bar = "this is a hidden global variable";
}

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

Другая непредвиденная глобальная переменная может быть созданаthisСоздайте:

function foo() {
    this.variable = "potential accidental global";
}
// foo 调用自己,this 指向了全局对象(window)
foo();

Добавьте «use strict» в заголовок вашего файла JavaScript, чтобы избежать таких ошибок. Включите синтаксический анализ JavaScript в строгом режиме, чтобы избежать непреднамеренных глобальных переменных.

2. Забытый таймер или функция обратного вызова

var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        // 处理 node 和 someResource
        node.innerHTML = JSON.stringify(someResource));
    }
}, 1000);

Такой код встречается очень часто, если элемент с id Node удалить из DOM, то таймер все равно будет существовать, в то же время, поскольку callback-функция содержит ссылку на someResource, то someResource вне таймера освобождаться не будет.

3. Закрытие

function bindEvent(){
  var obj=document.createElement('xxx')
  obj.onclick=function(){
    // Even if it is a empty function
  }
}

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

// 将事件处理函数定义在外面
function bindEvent() {
  var obj = document.createElement('xxx')
  obj.onclick = onclickHandler
}
// 或者在定义事件处理函数的外部函数中,删除对dom的引用
function bindEvent() {
  var obj = document.createElement('xxx')
  obj.onclick = function() {
    // Even if it is a empty function
  }
  obj = null
}

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

4. Ссылки на элементы DOM, которые не очищаются

Иногда бывает полезно сохранить внутреннюю структуру данных узла DOM. Если вы хотите быстро обновить несколько строк таблицы, имеет смысл хранить каждую строку в DOM в виде словаря (пара ключ-значение JSON) или массива. На данный момент есть две ссылки на один и тот же элемент DOM: одна в дереве DOM и одна в словаре. Когда вы решите удалить эти строки в будущем, вам необходимо очистить обе ссылки.

var elements = {
    button: document.getElementById('button'),
    image: document.getElementById('image'),
    text: document.getElementById('text')
};
function doStuff() {
    image.src = 'http://some.url/image';
    button.click();
    console.log(text.innerHTML);
}
function removeButton() {
    document.body.removeChild(document.getElementById('button'));
    // 此时,仍旧存在一个全局的 #button 的引用
    // elements 字典。button 元素仍旧在内存中,不能被 GC 回收。
}

Хотя мы удалили кнопку с помощью removeChild, она по-прежнему содержит ссылку на #button в объекте elements, другими словами, элемент DOM все еще находится в памяти.

В-четвертых, метод идентификации утечки памяти

Новая версия хрома рассматривается в производительности:

шаг:

  • Открытые инструменты разработчика
  • Проверьте скриншоты и память
  • Маленькая точка в верхнем левом углу начинает запись (запись)
  • остановить запись

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

Некоторые способы избежать утечек памяти:

  • Сократите ненужные глобальные переменные или объекты с длительным жизненным циклом и своевременно выполняйте сборку мусора для бесполезных данных.
  • Обратите внимание на логику программы и избегайте «мертвой петли» и тому подобного.
  • Избегайте создания слишком большого количества объектов

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

V. Оптимизация сценариев использования сборки мусора

1. Оптимизация массива

Присвоение [] объекту массива — это ярлык для очистки массива (например: arr = [];), но следует отметить, что этот метод создает новый пустой объект и превращает исходный объект массива в небольшой кусок памяти. мусор! На самом деле, присвоение длины массива 0 (arr.length = 0) также может достичь цели очистки массива, и в то же время реализовать повторное использование массива и уменьшить генерацию мусора в памяти.

const arr = [1, 2, 3, 4];
console.log('浪里行舟');
arr.length = 0  // 可以直接让数字清空,而且数组类型不变。
// arr = []; 虽然让a变量成一个空数组,但是在堆上重新申请了一个空数组对象。

2. Максимально возможное повторное использование объектов

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

var t = {} // 每次循环都会创建一个新对象。
for (var i = 0; i < 10; i++) {
  // var t = {};// 每次循环都会创建一个新对象。
  t.age = 19
  t.name = '123'
  t.index = i
  console.log(t)
}
t = null //对象如果已经不用了,那就立即设置为null;等待垃圾回收。

3. Выражение функции в цикле можно использовать повторно, и лучше всего поместить его вне цикла.

// 在循环中最好也别使用函数表达式。
for (var k = 0; k < 10; k++) {
  var t = function(a) {
    // 创建了10次  函数对象。
    console.log(a)
  }
  t(k)
}
// 推荐用法
function t(a) {
  console.log(a)
}
for (var k = 0; k < 10; k++) {
  t(k)
}
t = null

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

Добро пожаловать в публичный аккаунт:Мастер по фронтенду, мы будем свидетелями вашего роста вместе!

image

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