我试图理解为什么下面的代码会导致内存泄漏
var aThing = null;
var outer = function() {
console.log('running');
var something = aThing;
var closure1 = function() {
if (something) {
console.log('something');
}
};
aThing = {
str: new Array(1000000).join('8'),
someMethod: function() {}
};
};
setInterval(outer, 1000);
这是显示内存从 Google Chrome 增加的时间线:
但是这段代码的微小变化不会导致相同的内存泄漏:
var aThing = null;
var outer = function() {
console.log('running');
var something = aThing;
var closure1 = function() {
if (something) {
console.log('something');
}
}
aThing = {
str: new Array(1000000).join('8')
};
function someMethod() {};
};
setInterval(outer, 1000);
这是显示 GC 清理正常的等效时间线。
我知道在第一个版本中存在内存泄漏,因为变量“something”没有被清理。为什么它在第二个示例中被 GC 而不是第一个?
最佳答案
主要的答案是,在您的第二个代码块中,没有直接引用任何一个闭包(closure1
或 someMethod
)在 outer< 的返回后仍然存在
(outer
之外的任何内容都指向它们),因此没有任何内容指向创建它们的上下文,并且可以清除该上下文。但是,在您的第二个代码块中,对 someMethod
的直接引用在返回后仍然存在,作为您分配给 aThing
的对象的一部分,因此上下文作为整体不能被 GC。
让我们看看您的第一个区 block 会发生什么:
在第一次执行 outer
之后,我们有(忽略一堆细节):
+−−−−−−−−−−−−−+ aThing−−−−−>| (object #1) | +−−−−−−−−−−−−−+ | str: ... | +−−−−−−−−−−−−−−−−−−−−+ | someMethod |−−−−>| (context #1) | +−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−−−+ | something: null | | closure1: function | +−−−−−−−−−−−−−−−−−−−−+
第二次执行后:
+−−−−−−−−−−−−−+ aThing−−−−−>| (object #2) | +−−−−−−−−−−−−−+ | str: ... | +−−−−−−−−−−−−−−−−−−−−+ | someMethod |−−−−>| (context #2) | +−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−+ | something |−−−−>| (object #1) | | closure1: function | +−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−−−+ | str: ... | +−−−−−−−−−−−−−−−−−−−−+ | someMethod |−−−−>| (context #1) | +−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−−−+ | something: null | | closure1: function | +−−−−−−−−−−−−−−−−−−−−+
在第三次执行之后:
+−−−−−−−−−−−−−+ aThing−−−−−>| (object #3) | +−−−−−−−−−−−−−+ | str: ... | +−−−−−−−−−−−−−−−−−−−−+ | someMethod |−−−−>| (context #3) | +−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−+ | something |−−−−>| (object #2) | | closure1: function | +−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−−−+ | str: ... | +−−−−−−−−−−−−−−−−−−−−+ | someMethod |−−−−>| (context #2) | +−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−+ | something |−−−−>| (object #1) | | closure1: function | +−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−−−+ | str: ... | +−−−−−−−−−−−−−−−−−−−−+ | someMethod |−−−−>| (context #1) | +−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−−−+ | something: null | | closure1: function | +−−−−−−−−−−−−−−−−−−−−+
你可以看到这是怎么回事。
由于第二个 block 从不保留对 closure1
或 someMethod
的引用,因此它们都不会在内存中保留上下文。
最初在 2015 年回答您的问题时,我有点惊讶 V8(Chrome 的 JavaScript 引擎)没有优化这个泄漏,因为只保留了 someMethod
,而 someMethod
实际上并没有使用 something
或 closure1
(或 eval
或 new Function
或 debugger
)。虽然理论上它通过上下文引用了它们,但静态分析会表明它们实际上无法使用,因此可以删除。但是闭包优化真的很容易被打扰,我猜里面有什么东西在打扰它,或者 V8 团队发现做那种级别的分析不值得运行时成本。我确实记得看到来自 V8 团队之一的推文说它过去比现在做更多的闭包优化(这次编辑是在 2021 年 9 月),因为这种权衡是不值得的。
关于闭包词法环境中的 JavaScript 内存泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34480663/