#memory-management #garbage-collection #v8
#управление памятью #сборка мусора #v8
Вопрос:
Взгляните на код. Предположим, что выполнение каждого оператора занимает 0 миллисекунд. printAfter2 — это простая функция, которая печатает строку, переданную ей после 2 секунд вызова.
printAfter2 = (obj) => {
setTimeout(() => {
console.log(JSON.stringify(obj));
}, 2000)
}
В приведенном ниже коде мы создали функцию, которая
-
определяет переменную obj с блочной областью в момент времени 0 мс
-
вызывает функцию с obj (тип — Object) в качестве параметра в момент времени 0 мс. Поскольку переданный параметр является объектом, поэтому его ссылка будет передана функции.
-
Затем есть консоль.вызов функции журнала. После этого блок завершается в момент времени 0 мс, поэтому переменная obj, ограниченная областью действия блока, также будет уничтожена.
-
В момент времени 2000 функция printAfter2 извлекает значение параметра, которое было передано ей. В данном случае это ссылка на переменную, которая пока должна быть уничтожена. Но это сработало не так, как ожидалось. Он печатает тот же исходный объект obj за 2000 мс, который должен был быть уничтожен за 0 мс. Почему это так?
На самом деле нам не нужна асинхронная функция, но мы игнорируем ее.
(async () => {
let obj = {name: 'Ali'}
printAfter2(obj);
console.log("obj var will be destroyed after this block");
})()
Ответ №1:
Когда переменная / параметр obj
выходит за пределы области видимости, это не означает, что что-либо немедленно уничтожается. Это означает, что исчезает только одна ссылка на какой-либо объект, что делает этот объект пригодным для сборки мусора тогда и только тогда, когда это была последняя ссылка на него. Сборщик мусора в конечном итоге (при следующем запуске) освободит память, принадлежащую объектам, которые больше недоступны, т. Е. Не Имеют ссылок на них. Давайте рассмотрим более простой случай, без каких-либо замыканий:
let o1;
function f1(obj) {
console.log(obj); // (3)
} // (4)
o1 = new Object(); // (1)
f1(o1); // (2)
let o2 = o1; // (5)
o1 = null; // (6)
// (7)
o2 = new Array();
// (8)
Строка (1), очевидно, выделяет объект и использует переменную o1
для ссылки на него. Обратите внимание, что существует различие между объектом и переменной; в частности, они имеют разное время жизни.
Строка (2) передает объект функции; во время выполнения функции (например, в строке (3)) есть две переменные, ссылающиеся на один и тот же объект: o1
во внешней области видимости и obj
в f1
области видимости.
Когда f1
завершается в строке (4), переменная obj
выходит за пределы области видимости, но объект по-прежнему доступен через o1
.
Строка (5) создает новую переменную, снова ссылающуюся на тот же объект. Концептуально это очень похоже на передачу его некоторой функции.
Когда o1
перестает ссылаться на объект в строке (6), это не делает объект пригодным для сборки мусора в строке (7), потому o2
что он все еще ссылается на него («сохраняя его живым»). o2
Объект становится недоступным только после переназначения или выхода из области видимости только один раз: если сборщик мусора запускается в любое время после того, как выполнение достигло строки (8), память объекта будет освобождена.
(Примечание: сборщик мусора на самом деле не «собирает мусор» или «уничтожает объекты», потому что он вообще не затрагивает эту память. Он регистрирует только тот факт, что память, в которой был сохранен объект, теперь свободна для использования для нового выделения.)
В случае вашего примера вы создаете замыкание () => console.log(JSON.stringify(obj))
, которое содержит ссылку на объект. Пока это закрытие ожидает своего времени для выполнения, эта ссылка сохранит объект живым. Он может быть освобожден только после того, как закрытие завершится и само станет недоступным.
Для иллюстрации по-другому:
function MakeClosure() {
let obj = {message: "Hello world"};
return function() { console.log(JSON.stringify(obj)); };
}
let callback = MakeClosure();
// While the local variable `obj` is inaccessible now, `callback` internally
// has a reference to the object created as `{message: ...}`.
setTimeout(callback, 2000);
// Same situation as above at this point.
callback = null;
// Now the variable `callback` can't be used any more to refer to the closure,
// but the `setTimeout` call added the closure to some internal list, so it's
// not unreachable yet.
// Only once the callback has run and is dropped from the engine-internal list
// of waiting setTimeout-scheduled callbacks, can the `{message: ...}` object get
// cleaned up -- again, this doesn't happen immediately, only whenever the garbage
// collector decides to run.