JavaScript封闭是如何被垃圾收集的

时间:2013-11-05 20:55:50

标签: javascript internet-explorer google-chrome firefox garbage-collection

我已经记录了以下Chrome bug,这导致我的代码中出现了许多严重且非显而易见的内存泄漏:

(这些结果使用运行GC的Chrome开发工具'memory profiler,然后获取未收集的所有内容的快照。)

在下面的代码中,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();

相应的截图:

screenshot of Chromebug

如果对象被同一上下文中的任何其他闭包引用,则闭包(在这种情况下,function() {})似乎保持所有对象“活着”,无论该闭包本身是否可以访问。

我的问题是关于其他浏览器(IE 9+和Firefox)的关闭垃圾收集。我对webkit的工具非常熟悉,比如JavaScript堆分析器,但我对其他浏览器的工具知之甚少,所以我无法对此进行测试。

在这三种情况中哪一种IE9 +和Firefox会垃圾收集 someClass 实例?

6 个答案:

答案 0 :(得分:77)

据我所知,这不是一个错误,而是预期的行为。

来自Mozilla的Memory management page:“截至2012年,所有现代浏览器都提供了一个标记 - 清除垃圾收集器。” “限制:对象需要明确无法访问

在您失败的示例中some仍然可以在闭包中找到。我尝试了两种方法使它无法访问并且都可以工作。您可以在不再需要时设置some=null,或者设置window.f_ = null;它就会消失。

<强>更新

我在Windows上的Chrome 30,FF25,Opera 12和IE10中尝试过它。

standard没有说垃圾收集,但提供了一些应该发生的线索。

  • 第13节函数定义,第4步:“让闭包成为创建13.2中指定的新Function对象的结果”
  • 第13.2节“范围指定的词汇环境”(范围=关闭)
  • 第10.2节词汇环境:
  

“(内部)词汇环境的外部引用是逻辑上对词汇环境的引用   环绕着内在的词汇环境。

     

外部词汇环境当然可以有自己的外部   词汇环境。词汇环境可以作为多个内部词汇的外部环境   环境。例如,如果函数声明包含两个嵌套的函数声明,那么词汇表   每个嵌套函数的环境都将Lexical作为其外部词汇环境   当前执行周围功能的环境。“

因此,函数可以访问父项的环境。

因此,some应该在返回函数的闭包中可用。

那为什么不总是可用?

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

可以改进GC以检测函数中是否使用return null,但它会很复杂。

一个不好的例子:

some

在上面的例子中,GC无法知道变量是否被使用(代码经过测试,可在Chrome30,FF25,Opera 12和IE10中使用)。

如果通过为var someClass = function() {}; function f() { var some = new someClass(); return function(code) { console.log(eval(code)); }; } window.f_ = f(); window.f_('some'); 分配另一个值来破坏对象的引用,则会释放内存。

在我看来,这不是一个错误。

答案 1 :(得分:48)

我在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);

直播网站here

我希望用最少的内存来结束500 function() {}的数组。

不幸的是,事实并非如此。每个空函数都持有一个(永远无法访问但不是GC)数百万个数组。

Chrome最终停止并死机,Firefox使用了近4GB的RAM后完成了整个过程,并且IE渐渐渐渐变慢,直到显示“内存不足”为止。

删除其中一条注释行可修复所有内容。

似乎所有这三种浏览器(Chrome,Firefox和IE)都保留了每个上下文的环境记录,而不是每个闭包。鲍里斯假设这个决定背后的原因是表现,这似乎很可能,虽然我不确定如何根据上述实验调用它。

如果需要一个引用some的闭包(授予我在这里没有使用它,但想象我做了),如果不是

function g() { some; }

我用

var g = (function(some) { return function() { some; }; )(some);

它会通过将闭包移动到与我的其他函数不同的上下文来解决内存问题。

这将使我的生活更加乏味。

P.S。出于好奇,我在Java中尝试了这一点(使用它在函数内部定义类的能力)。 GC的工作方式与我原先希望的Javascript一样。

答案 2 :(得分:15)

启发式方法各不相同,但实现此类操作的常用方法是为您的案例中的f()每次调用创建一个环境记录,并仅存储实际关闭的f的本地文件在该环境记录中(通过一些闭包)。然后,在f调用中创建的任何闭包都会使环境记录保持活动状态。我相信这就是Firefox实现闭包的方式,至少。

这具有快速访问封闭变量和实现简单的优点。它具有观察到的效果的缺点,其中关闭某个变量的短暂闭合使得它通过长寿命闭合保持活着。

可以尝试为不同的闭包创建多个环境记录,具体取决于它们实际关闭的内容,但是这会很快变得非常复杂并且可能导致其自身的性能和内存问题......

答案 3 :(得分:0)

  1. 在函数调用之间保持状态 假设您有函数add(),并且希望它在几次调用中将传递给它的所有值相加并返回总和。

喜欢 add(5); //返回5

add(20); //返回25(5 + 20)

add(3); //返回28(25 + 3)

您首先可以通过两种方式正常地定义全局变量 当然,您可以使用全局变量来保存总数。但是请记住,如果您(不使用)全局变量,那么这个家伙会吞噬您的生命。

最新方法使用闭包,而没有定义全局变量

//public class ComplicatedDuck extends Duck (Java)
    public class ComplicatedDuck : Duck 
    {
      public ComplicatedDuck(IFlybehaviour flybehaviour)
      {
        flybehaviour = new GeneralFlybehaviour();
      }

     public void GrantWishes()
     { 
       Console.WriteLine("Wish Granted")
     }
    }

答案 4 :(得分:0)

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

答案 5 :(得分:0)

(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
    
  
}());