JavaScriptclosures如何收集垃圾
我logging了以下Chrome错误 ,导致我的代码中出现了许多严重和不明显的内存泄漏:
(这些结果使用Chrome开发工具的内存分析器 ,该分析器运行GC,然后获取所有未收集垃圾的堆快照。)
在下面的代码中, someClass
实例是垃圾收集(好):
var someClass = function() {}; function f() { var some = new someClass(); return function() {}; } window.f_ = f();
但在这种情况下不会被垃圾收集(坏):
var someClass = function() {}; function f() { var some = new someClass(); function unreachable() { some; } return function() {}; } window.f_ = f();
和相应的截图:
看来,闭包(在这种情况下, function() {}
)保持所有对象“活着”,如果该对象被相同的上下文中的任何其他闭包引用,无论该闭包本身是否可以连接。
我的问题是在其他浏览器(IE 9 +和Firefox)中closures垃圾收集。 我对webkit的工具非常熟悉,比如JavaScript堆分析器,但是我对其他浏览器的工具知之甚less,所以我一直无法testing它。
在这三种情况下,IE9 +和Firefox垃圾收集 someClass
实例?
据我所知,这不是一个错误,而是预期的行为。
从Mozilla的“ 内存pipe理”页面 :“截至2012年,所有现代浏览器都发布了标记式的垃圾收集器。” “限制: 对象需要明确无法访问 ” 。
在你的例子中,它的失败仍然可以在封闭中find。 我尝试了两种方法使其无法访问,并且都能正常工作。 当你不需要它的时候,你可以设置some=null
,或者你设置window.f_ = null;
它将会消失。
更新
我已经在Windows上使用了Chrome 30,FF25,Opera 12和IE10。
标准没有说垃圾收集的任何内容,但给出了一些应该发生的线索。
- 第13节函数定义,第4步:“让闭包是按照13.2的规定创build一个新的函数对象的结果”
- 第13.2节“范围指定的词汇环境”(范围=closures)
- 第10.2节词汇环境:
“(内部)词汇环境的外部参照是对词汇环境的一种参考,它在逻辑上围绕着内部词汇环境。
外部词汇环境当然可以有其自己的外部词汇环境。 词汇环境可以作为多个内部词汇环境的外部环境。 例如,如果函数声明包含两个嵌套的函数声明,则每个嵌套函数的词法环境将具有当前执行周围函数的词法环境作为它们的外部词法环境。
所以,一个函数将有权访问父级的环境。
所以, some
应该在返回函数的closures中可用。
那为什么不总是可用?
看起来,Chrome和FF在某些情况下足够聪明以消除variables,但在Opera和IE中, some
variables在闭包中可用(注意:在return null
查看此设置断点并检查debugging器)。
GC可以改进以检测function中是否使用了some
function,但是它会变得复杂。
一个不好的例子:
var someClass = function() {}; function f() { var some = new someClass(); return function(code) { console.log(eval(code)); }; } window.f_ = f(); window.f_('some');
在上面的例子中,GC无法知道variables是否被使用(代码testing,并在Chrome30,FF25,Opera 12和IE10中工作)。
如果通过为window.f_
指定另一个值来破坏对象的引用,则会释放内存。
在我看来,这不是一个错误。
我在IE9 +和Firefox中testing了这个。
function f() { var some = []; while(some.length < 1e6) { some.push(some.length); } function g() { some; } //removing this fixes a massive memory leak return function() {}; //or removing this } var a = []; var interval = setInterval(function() { var len = a.push(f()); if(len >= 500) { clearInterval(interval); } }, 10);
住在这里 。
我希望用最less的内存来结束500个function() {}
的数组。
不幸的是,情况并非如此。 每一个空的函数都保存在一个有100万个数字的(永远不可达的,但不是GC的)数组中。
Chrome最终停止和死亡,Firefox使用近4GB内存后完成整个事情,并且IE越来越慢,直到它显示“内存不足”。
删除任何一个注释行可以修复一切。
看来,所有这三个浏览器(Chrome浏览器,Firefox和IE)保持每个上下文的环境logging,而不是每个闭包。 鲍里斯假设这个决定背后的原因是性能,这似乎很可能,虽然我不确定如何根据上述实验调用它的性能。
如果需要引用some
闭包(当然,我没有在这里使用它,但想象我做了),而不是
function g() { some; }
我用
var g = (function(some) { return function() { some; }; )(some);
它将通过将闭包移到与我的其他函数不同的上下文来解决内存问题。
这会使我的生活更乏味。
PS出于好奇,我在Java中尝试了这个(使用它的能力来定义函数内部的类)。 GC按照我原先希望使用Javascript的方式工作。
启发式有所不同,但实现这种事情的一种常见方式是为每个对f()
调用创build一个环境logging,并且只保存在该环境中实际closures的f
的局部(通过一些闭包)logging。 然后,在f
调用中创build的任何闭包都会保留环境logging。 我相信这是Firefox至less如何实现闭包。
这具有快速访问闭合variables和简单实施的好处。 它有观察到的效果的缺点,一个短暂的封闭closures了一些variables,导致它通过长期closures而维持生命。
人们可以尝试为不同的closures创build多个环境logging,具体取决于他们实际closures的内容,但是这可能会非常迅速地变得非常复杂,并可能导致其自身的性能和内存问题。