JavaScript 闭包如何垃圾回收
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19798803/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How JavaScript closures are garbage collected
提问by Paul Draper
I've logged the following Chrome bug, which has led to many serious and non-obvious memory leaks in my code:
我记录了以下Chrome 错误,这导致我的代码中出现了许多严重且不明显的内存泄漏:
(These results use Chrome Dev Tools' memory profiler, which runs the GC, and then takes a heap snapshot of everything not garbaged collected.)
(这些结果使用 Chrome Dev Tools 的内存分析器,它运行 GC,然后对所有没有被垃圾收集的内容进行堆快照。)
In the code below, the someClass
instance is garbage collected (good):
在下面的代码中,someClass
实例被垃圾收集(好):
var someClass = function() {};
function f() {
var some = new someClass();
return function() {};
}
window.f_ = f();
But it won't be garbage collected in this case (bad):
但在这种情况下它不会被垃圾收集(坏):
var someClass = function() {};
function f() {
var some = new someClass();
function unreachable() { some; }
return function() {};
}
window.f_ = f();
And the corresponding screenshot:
以及相应的截图:
It seems that a closure (in this case, function() {}
) keeps all objects "alive" if the object is referenced by any other closure in the same context, whether or not if that closure itself is even reachable.
function() {}
如果该对象被同一上下文中的任何其他闭包引用,则闭包(在这种情况下,)似乎使所有对象保持“活动”状态,无论该闭包本身是否可访问。
My question is about garbage collection of closure in other browsers (IE 9+ and Firefox). I am quite familiar with webkit's tools, such as the JavaScript heap profiler, but I know little of other browsers' tools, so I haven't been able to test this.
我的问题是关于其他浏览器(IE 9+ 和 Firefox)中关闭的垃圾收集。我非常熟悉 webkit 的工具,例如 JavaScript 堆分析器,但我对其他浏览器的工具知之甚少,因此我无法对此进行测试。
In which of these three cases will IE9+ and Firefox garbage collect thesomeClass
instance?
在这三种情况下,IE9+ 和 Firefox 会垃圾收集someClass
实例中的哪一种?
采纳答案by Paul Draper
I tested this in IE9+ and Firefox.
我在 IE9+ 和 Firefox 中对此进行了测试。
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);
Live site here.
现场直播在这里。
I hoped to wind up with an array of 500 function() {}
's, using minimal memory.
我希望function() {}
使用最少的内存以 500 的数组结束。
Unfortunately, that was not the case. Each empty function holds on to an (forever unreachable, but not GC'ed) array of a million numbers.
不幸的是,情况并非如此。每个空函数都持有一个(永远无法访问,但不是 GC 处理的)一百万个数字的数组。
Chrome eventually halts and dies, Firefox finishes the whole thing after using nearly 4GB of RAM, and IE grows asymptotically slower until it shows "Out of memory".
Chrome 最终停止并死亡,Firefox 在使用了近 4GB 的 RAM 后完成了整个过程,而 IE 逐渐变慢,直到显示“内存不足”。
Removing either one of the commented lines fixes everything.
删除任一注释行即可解决所有问题。
It seems that all three of these browsers (Chrome, Firefox, and IE) keep an environment record per context, not per closure. Boris hypothesizes the reason behind this decision is performance, and that seems likely, though I'm not sure how performant it can be called in light of the above experiment.
似乎所有这三种浏览器(Chrome、Firefox 和 IE)都为每个上下文保存环境记录,而不是每个闭包。Boris 假设这个决定背后的原因是性能,这似乎很有可能,尽管我不确定根据上述实验可以调用它的性能如何。
If a need a closure referencing some
(granted I didn't use it here, but imagine I did), if instead of
如果需要闭包引用some
(当然我没有在这里使用它,但想象我这样做了),如果不是
function g() { some; }
I use
我用
var g = (function(some) { return function() { some; }; )(some);
it will fix the memory problems by moving the closure to a different context than my other function.
它将通过将闭包移动到与我的其他函数不同的上下文来解决内存问题。
This will make my life much more tedious.
这将使我的生活更加乏味。
P.S. Out of curiousity, I tried this in Java (using its ability to define classes inside of functions). GC works as I had originally hoped for Javascript.
PS出于好奇,我在Java中尝试了这个(使用它在函数内部定义类的能力)。GC 像我最初希望的 Javascript 一样工作。
回答by some
As far as I can tell, this is not a bug but the expected behavior.
据我所知,这不是错误,而是预期的行为。
From Mozilla's Memory management page: "As of 2012, all modern browsers ship a mark-and-sweep garbage-collector." "Limitation: objects need to be made explicitly unreachable".
来自 Mozilla 的内存管理页面:“截至 2012 年,所有现代浏览器都提供标记和清除垃圾收集器。” “限制:对象需要明确地无法访问”。
In your examples where it fails some
is still reachable in the closure. I tried two ways to make it unreachable and both work. Either you set some=null
when you don't need it anymore, or you set window.f_ = null;
and it will be gone.
在您的示例中some
,在闭包中仍然可以访问它失败的示例。我尝试了两种方法使其无法访问并且都有效。要么some=null
在不再需要它时设置,要么设置后window.f_ = null;
它就会消失。
Update
更新
I have tried it in Chrome 30, FF25, Opera 12 and IE10 on Windows.
我已经在 Windows 上的 Chrome 30、FF25、Opera 12 和 IE10 中尝试过。
The standarddoesn't say anything about garbage collection, but gives some clues of what should happen.
该标准没有说明垃圾收集,但提供了一些应该发生什么的线索。
- Section 13 Function definition, step 4: "Let closure be the result of creating a new Function object as specified in 13.2"
- Section 13.2 "a Lexical Environment specified by Scope" (scope = closure)
- Section 10.2 Lexical Environments:
- 第 13 节函数定义,第 4 步:“让闭包成为创建 13.2 中指定的新函数对象的结果”
- 第 13.2 节“范围指定的词法环境”(范围 = 闭包)
- 第 10.2 节词法环境:
"The outer reference of a (inner) Lexical Environment is a reference to the Lexical Environment that logically surrounds the inner Lexical Environment.
An outer Lexical Environment may, of course, have its own outer Lexical Environment. A Lexical Environment may serve as the outer environment for multiple inner Lexical Environments. For example, if a Function Declarationcontains two nested Function Declarationsthen the Lexical Environments of each of the nested functions will have as their outer Lexical Environment the Lexical Environment of the current execution of the surrounding function."
“(内部)词法环境的外部引用是对逻辑上围绕内部词法环境的词法环境的引用。
当然,外部词法环境可能有自己的外部词法环境。一个词法环境可以作为多个内部词法环境的外部环境。例如,如果一个函数声明包含两个嵌套的函数声明,那么每个嵌套函数的词法环境将把当前执行周围函数的词法环境作为它们的外部词法环境。”
So, a function will have access to the environment of the parent.
因此,函数将可以访问父级的环境。
So, some
should be available in the closure of the returning function.
所以,some
应该在返回函数的关闭中可用。
Then why isn't it always available?
那为什么不总是可用呢?
It seems that Chrome and FF is smart enough to eliminate the variable in some cases, but in both Opera and IE the some
variable is available in the closure (NB: to view this set a breakpoint on return null
and check the debugger).
Chrome 和 FF 似乎足够聪明,可以在某些情况下消除变量,但在 Opera 和 IE 中,some
变量在闭包中可用(注意:查看此设置断点return null
并检查调试器)。
The GC could be improved to detect if some
is used or not in the functions, but it will be complicated.
GC 可以改进以检测some
函数中是否使用,但它会很复杂。
A bad example:
一个不好的例子:
var someClass = function() {};
function f() {
var some = new someClass();
return function(code) {
console.log(eval(code));
};
}
window.f_ = f();
window.f_('some');
In example above the GC has no way of knowing if the variable is used or not (code tested and works in Chrome30, FF25, Opera 12 and IE10).
在上面的例子中,GC 无法知道变量是否被使用(代码在 Chrome30、FF25、Opera 12 和 IE10 中测试和工作)。
The memory is released if the reference to the object is broken by assigning another value to window.f_
.
如果通过为 分配另一个值破坏了对对象的引用,则释放内存window.f_
。
In my opinion this isn't a bug.
在我看来,这不是一个错误。
回答by Boris Zbarsky
Heuristics vary, but a common way to implement this sort of thing is to create an environment record for each call to f()
in your case, and only store the locals of f
that are actually closed over (by someclosure) in that environment record. Then any closure created in the call to f
keeps alive the environment record. I believe this is how Firefox implements closures, at least.
启发式方法各不相同,但实现此类事情的一种常见方法是为f()
您的案例中的每个调用创建一个环境记录,并且仅在该环境记录中存储f
实际关闭(通过某种关闭)的本地变量。然后在调用中创建的任何闭包f
使环境记录保持活动状态。我相信这至少是 Firefox 实现闭包的方式。
This has the benefits of fast access to closed-over variables and simplicity of implementation. It has the drawback of the observed effect, where a short-lived closure closing over some variable causes it to be kept alive by long-lived closures.
这具有快速访问封闭变量和实现简单的好处。它具有观察到的效果的缺点,即关闭某些变量的短期闭包会导致它通过长期闭包保持活动状态。
One could try creating multiple environment records for different closures, depending on what they actually close over, but that can get very complicated very quickly and can cause performance and memory problems of its own...
可以尝试为不同的闭包创建多个环境记录,具体取决于它们实际关闭的内容,但这会很快变得非常复杂,并可能导致其自身的性能和内存问题......
回答by Avinash Maurya
- Maintain State between function calls Let's say you have function add() and you would like it to add all the values passed to it in several calls and return the sum.
- 在函数调用之间维护状态 假设您有函数 add() 并且您希望它添加在多次调用中传递给它的所有值并返回总和。
like add(5); // returns 5
像添加(5);// 返回 5
add(20); // returns 25 (5+20)
添加(20);// 返回 25 (5+20)
add(3); // returns 28 (25 + 3)
添加(3);// 返回 28 (25 + 3)
two way you can do this first is normal define a global variableOf course, you can use a global variable in order to hold the total. But keep in mind that this dude will eat you alive if you (ab)use globals.
两种方法可以做到这一点,首先是正常定义一个全局变量当然,你可以使用一个全局变量来保存总数。但是请记住,如果您(ab)使用全局变量,这个家伙会活活吃掉您。
now latest way using closurewith out define global variable
现在使用闭包而不定义全局变量的最新方法
(function(){
var addFn = function addFn(){
var total = 0;
return function(val){
total += val;
return total;
}
};
var add = addFn();
console.log(add(5));
console.log(add(20));
console.log(add(3));
}());
回答by Avinash Maurya
function Country(){
console.log("makesure country call");
return function State(){
var totalstate = 0;
if(totalstate==0){
console.log("makesure statecall");
return function(val){
totalstate += val;
console.log("hello:"+totalstate);
return totalstate;
}
}else{
console.log("hey:"+totalstate);
}
};
};
var CA=Country();
var ST=CA();
ST(5); //we have add 5 state
ST(6); //after few year we requare have add new 6 state so total now 11
ST(4); // 15
var CB=Country();
var STB=CB();
STB(5); //5
STB(8); //13
STB(3); //16
var CX=Country;
var d=Country();
console.log(CX); //store as copy of country in CA
console.log(d); //store as return in country function in d
回答by Avinash Maurya
(function(){
function addFn(){
var total = 0;
if(total==0){
return function(val){
total += val;
console.log("hello:"+total);
return total+9;
}
}else{
console.log("hey:"+total);
}
};
var add = addFn();
console.log(add);
var r= add(5); //5
console.log("r:"+r); //14
var r= add(20); //25
console.log("r:"+r); //34
var r= add(10); //35
console.log("r:"+r); //44
var addB = addFn();
var r= addB(6); //6
var r= addB(4); //10
var r= addB(19); //29
}());