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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-27 16:50:11  来源:igfitidea点击:

How JavaScript closures are garbage collected

javascriptinternet-explorergoogle-chromefirefoxgarbage-collection

提问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 someClassinstance 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:

以及相应的截图:

screenshot of Chromebug

Chromebug 的屏幕截图

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 thesomeClassinstance?

在这三种情况下,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 someis still reachable in the closure. I tried two ways to make it unreachable and both work. Either you set some=nullwhen 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, someshould 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 somevariable is available in the closure (NB: to view this set a breakpoint on return nulland check the debugger).

Chrome 和 FF 似乎足够聪明,可以在某些情况下消除变量,但在 Opera 和 IE 中,some变量在闭包中可用(注意:查看此设置断点return null并检查调试器)。

The GC could be improved to detect if someis 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 fthat are actually closed over (by someclosure) in that environment record. Then any closure created in the call to fkeeps 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

  1. 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.
  1. 在函数调用之间维护状态 假设您有函数 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
    
  
}());