`
oznyang
  • 浏览: 158923 次
  • 性别: Icon_minigender_1
  • 来自: 南京
文章分类
社区版块
存档分类
最新评论

IE下闭包引起跨页面内存泄露探讨

阅读更多
在ie的内存泄露中跨页面的泄露是最严重的,浏览器刷新了仍然无法释放掉泄露占用的资源,造成访问速度越来越慢,内存占用越来越大
closure引起cross page leak的主要原因是closure和dom元素的互相引用
看这个例子:
<div id="bb"><div id="aa">cc</div></div>
<script type="text/javascript">
function leakTest(){
	var a=[];//用来加大闭包资源占用,方便观察
	for(var i=0;i<100000;i++){
	 a.push('a');
	}
	var divA=document.getElementById('aa');
	divA.kk=function(){};
	divA.parentNode.removeChild(divA);
}
leakTest();
</script>

    用sIEve看下发现这个页面每次刷新都会产生跨页面泄露,ie内存占用大了7MB,具体fins的文章中有过介绍
在这个例子中我们在leakTest()中创建了一个内部匿名函数并在dom元素aa的自定义属性kk中保存了他的引用,这就产生了一个闭包
divA.parentNode.removeChild(divA);
这句是产生泄露的主要原因,移除了aa并不会使这个节点消失,只不过在dom树上无法访问这个节点而已,由于闭包的存在divA这个局部变量不会被释放,而divA中保存着aa的引用,这就形成了一个循环引用,闭包保存了dom元素aa的引用,dom元素aa的自定义属性kk又保存了闭包内部的匿名函数的引用,所以在页面刷新的时候IE无法释放掉这个aa和闭包的资源,在我这个例子中就比较吓人,刷一下涨几MB内存
    让我们删掉divA.parentNode.removeChild(divA);这句试试,发现没有泄露发生
    我推测IE在刷新时会强行释放掉dom树上的元素,而不存在于dom树中的节点不会强行释放,所以造成了跨页面泄露,这是我的个人推测,有别的意见欢迎讨论
怎么解决这个问题呢,其实我们只要打断引用链就行了
<div id="bb"><div id="aa">cc</div></div>
<script type="text/javascript">
function leakTest(){
	var a=[];
	for(var i=0;i<100000;i++){
	 a.push('a');
	}
	var divA=document.getElementById('aa');
	divA.kk=function(){};
	divA.parentNode.removeChild(divA);
	divA=null;
}
leakTest();
</script>

或者
<div id="bb"><div id="aa">cc</div></div>
<script type="text/javascript">
function leakTest(){
	var a=[];
	for(var i=0;i<100000;i++){
	 a.push('a');
	}
	document.getElementById('aa').kk=function(){};
	document.getElementById('aa').parentNode.removeChild(document.getElementById('aa'));
	//这个例子不保存aa的应用,也不会引起泄露
}
leakTest();
</script>

or
<div id="bb"><div id="aa">cc</div></div>
<script type="text/javascript">
function leakTest(){
	var a=[];
	for(var i=0;i<100000;i++){
	 a.push('a');
	}
	var divA=document.getElementById('aa');
	divA.kk=function(){};
	divA.parentNode.removeChild(divA);
	return divA;
}
var divA=leakTest();
divA.kk=null; //这个可以看到内存占用比上面少了7MB,因为解除了对闭包内部函数的引用,闭包占用的资源被释放了
</script>

通过上面的例子可以看出,如果某个函数中dom元素保存了内部函数的引用,就会形成闭包,很容易引起泄露,务必小心
另firefox下测试是没有这些问题的

分享到:
评论
37 楼 pipilu 2009-02-06  
trarck 写道
请注意:
  这是楼主的代码,也是会产生内存泄露的代码:
  
<div id="bb"><div id="aa">cc</div></div>  
    <script type="text/javascript">  
    function leakTest(){  
        var a=[];//用来加大闭包资源占用,方便观察  
       for(var i=0;i<100000;i++){  
         a.push('a');  
        }  
       var divA=document.getElementById('aa');  
        divA.kk=function(){};  
       divA.parentNode.removeChild(divA);  
   }  
   leakTest();  
    </script>  

   这是liudaoru的代码,但没有产生内存泄露(不信你可以试试)
  
 <div id="bb"><div id="aa">cc</div></div>     
 <script type="text/javascript">     
 function leakTest(){     
     var a=[];//用来加大闭包资源占用,方便观察     
     for(var i=0;i<100000;i++){     
      a.push('a');     
     }     
     this.divA=document.getElementById('aa');     
     this.divA.kk=function(){};     
     document.getElementById("bb").removeChild(this.divA);     
 }     
 new leakTest();  //产生个匿名对象   
 </script> 


为什么呢?如此相似的代码,一个产生了泄露一个却没有。
个人观点:
楼主用一个var定义了个局部变量divA,闭包<=>div就有了循环引用。
liudaoru用this。this.divA没有被闭包占用(可能是this的特殊性)。于是就有了,匿名对象->divA->闭包,闭包却没有引用diva,也就没有循环引用。


估计是因为闭包只引用函数变量(印象中,闭包是面向函数的编程时引入的概念),而this.divA实际上是leakTest的成员变量(实例变量),是外部可以访问的变量。
所以闭包并没有对this.divA的引用。
如果把上面的第二个代码改成:
 <div id="bb"><div id="aa">cc</div></div>     
 <script type="text/javascript">     
 function leakTest(){     
     var a=[];//用来加大闭包资源占用,方便观察     
     for(var i=0;i<100000;i++){     
      a.push('a');     
     }     
     this.divA=document.getElementById('aa');  
     var self = this.divA;
     this.divA.kk=function(){};     
     document.getElementById("bb").removeChild(this.divA);     
 }     
 new leakTest();  //产生个匿名对象   
 </script> 


这样闭包中有了对self的引用,因此就行成了this.divA对DOM节点( document.getElementById('aa') )的引用,而DOM节点又有对this.divA的引用,这就行成了循环引用(circular reference),资源无法回收。
36 楼 javascripteye 2009-02-05  
引用计数的gc方式是不大可能处理js对象和dom对象相互引用的,最好的处理方法是时刻保持清醒即时断开这种互引用就行了。
35 楼 likehibernate 2009-02-05  
这好像是IE的内存管理问题,好像在哪里看到,如果js与js对象互引用不会出现这种情况,如果js与native object互引用特别是用到闭包那这种内存泄露存在的可能性是极高的。
比较可笑的是有人一直去怪IE的内存管理而不去找到让其内存泄露的原因并改之!
34 楼 sutra 2008-08-22  
achun 写道
有深度呀,第一次看到这样的文章,长知识了。
顺手测试了一下www.jquery.com似乎同样有泄露,
不过我不知道,leaks栏中有红色的和绿色的差别是什么呀?
看来写js还要考虑leak问题呀!
做程序真.....的很...苦......
有泄露就.....要修...补......
啊.........

根据我的经验,红色表示你上次查看(show leaks)后新出现的leaks。
33 楼 trarck 2008-08-14  
请注意:
  这是楼主的代码,也是会产生内存泄露的代码:
  
<div id="bb"><div id="aa">cc</div></div>  
    <script type="text/javascript">  
    function leakTest(){  
        var a=[];//用来加大闭包资源占用,方便观察  
       for(var i=0;i<100000;i++){  
         a.push('a');  
        }  
       var divA=document.getElementById('aa');  
        divA.kk=function(){};  
       divA.parentNode.removeChild(divA);  
   }  
   leakTest();  
    </script>  

   这是liudaoru的代码,但没有产生内存泄露(不信你可以试试)
  
 <div id="bb"><div id="aa">cc</div></div>     
 <script type="text/javascript">     
 function leakTest(){     
     var a=[];//用来加大闭包资源占用,方便观察     
     for(var i=0;i<100000;i++){     
      a.push('a');     
     }     
     this.divA=document.getElementById('aa');     
     this.divA.kk=function(){};     
     document.getElementById("bb").removeChild(this.divA);     
 }     
 new leakTest();  //产生个匿名对象   
 </script> 


为什么呢?如此相似的代码,一个产生了泄露一个却没有。
个人观点:
楼主用一个var定义了个局部变量divA,闭包<=>div就有了循环引用。
liudaoru用this。this.divA没有被闭包占用(可能是this的特殊性)。于是就有了,匿名对象->divA->闭包,闭包却没有引用diva,也就没有循环引用。
32 楼 trarck 2008-08-14  
liudaoru 写道
那么这种情况该如何解释那?这种情况也没有太大的内存泄漏
<div id="bb"><div id="aa">cc</div></div>   
<script type="text/javascript">   
function leakTest(){   
    var a=[];//用来加大闭包资源占用,方便观察   
    for(var i=0;i<100000;i++){   
     a.push('a');   
    }   
    this.divA=document.getElementById('aa');   
    this.divA.kk=function(){};   
   	document.getElementById("bb").removeChild(this.divA);   
}   
new leakTest();   
</script>
 

你确定上面代码,IE下会产生内存泄露?
31 楼 Relucent 2008-07-07  
这么说遇到 IE 局部变量还是最后设 NULL比较安区
30 楼 soni 2008-06-25  
这属于IE的问题,关js鸟事。
29 楼 csf177 2008-06-25  
IE7疯了 没什么说的了
建议大家别试图处理IE7的内存泄露
28 楼 ericxu131 2008-06-24  
这个例子是不是就是说IE会强制释放可以访问的dom元素的资源
而divA.parentNode.removeChild(divA); 将divA这个所引用的div隐藏了,或者说设置为不可访问了,这个时候闭包里面引用的divA这个资源无法被释放,最终导致了内存泄漏??
27 楼 penghao122 2008-04-16  
收藏...竟自动回贴了.不好意思

26 楼 penghao122 2008-04-16  
oznyang 写道
在ie的内存泄露中跨页面的泄露是最严重的,浏览器刷新了仍然无法释放掉泄露占用的资源,造成访问速度越来越慢,内存占用越来越大
closure引起cross page leak的主要原因是closure和dom元素的互相引用
看这个例子:
<div id="bb"><div id="aa">cc</div></div>
<script type="text/javascript">
function leakTest(){
	var a=[];//用来加大闭包资源占用,方便观察
	for(var i=0;i<100000;i++){
	 a.push('a');
	}
	var divA=document.getElementById('aa');
	divA.kk=function(){};
	divA.parentNode.removeChild(divA);
}
leakTest();
</script>

    用sIEve看下发现这个页面每次刷新都会产生跨页面泄露,ie内存占用大了7MB,具体fins的文章中有过介绍
在这个例子中我们在leakTest()中创建了一个内部匿名函数并在dom元素aa的自定义属性kk中保存了他的引用,这就产生了一个闭包
divA.parentNode.removeChild(divA);
这句是产生泄露的主要原因,移除了aa并不会使这个节点消失,只不过在dom树上无法访问这个节点而已,由于闭包的存在divA这个局部变量不会被释放,而divA中保存着aa的引用,这就形成了一个循环引用,闭包保存了dom元素aa的引用,dom元素aa的自定义属性kk又保存了闭包内部的匿名函数的引用,所以在页面刷新的时候IE无法释放掉这个aa和闭包的资源,在我这个例子中就比较吓人,刷一下涨几MB内存
    让我们删掉divA.parentNode.removeChild(divA);这句试试,发现没有泄露发生
    我推测IE在刷新时会强行释放掉dom树上的元素,而不存在于dom树中的节点不会强行释放,所以造成了跨页面泄露,这是我的个人推测,有别的意见欢迎讨论
怎么解决这个问题呢,其实我们只要打断引用链就行了
<div id="bb"><div id="aa">cc</div></div>
<script type="text/javascript">
function leakTest(){
	var a=[];
	for(var i=0;i<100000;i++){
	 a.push('a');
	}
	var divA=document.getElementById('aa');
	divA.kk=function(){};
	divA.parentNode.removeChild(divA);
	divA=null;
}
leakTest();
</script>

或者
<div id="bb"><div id="aa">cc</div></div>
<script type="text/javascript">
function leakTest(){
	var a=[];
	for(var i=0;i<100000;i++){
	 a.push('a');
	}
	document.getElementById('aa').kk=function(){};
	document.getElementById('aa').parentNode.removeChild(document.getElementById('aa'));
	//这个例子不保存aa的应用,也不会引起泄露
}
leakTest();
</script>

or
<div id="bb"><div id="aa">cc</div></div>
<script type="text/javascript">
function leakTest(){
	var a=[];
	for(var i=0;i<100000;i++){
	 a.push('a');
	}
	var divA=document.getElementById('aa');
	divA.kk=function(){};
	divA.parentNode.removeChild(divA);
	return divA;
}
var divA=leakTest();
divA.kk=null; //这个可以看到内存占用比上面少了7MB,因为解除了对闭包内部函数的引用,闭包占用的资源被释放了
</script>

通过上面的例子可以看出,如果某个函数中dom元素保存了内部函数的引用,就会形成闭包,很容易引起泄露,务必小心
另firefox下测试是没有这些问题的


25 楼 笨笨狗 2008-04-15  
我从来不让循环链发生,所以这种问题还是可以避免的
24 楼 afcn0 2008-04-15  
什么情况下使用闭包?什么情况下都会有闭包,只不过是你想说是不是用匿名函数直接量把你想闭的闭进去还是不闭进去
23 楼 xieye 2008-04-15  
看了各位高手的解释,我似乎清楚,似乎又糊涂。
那这么说,闭包还是少用的好吗?

什么情况下可以方便使用闭包呢,我是一直都在大量使用闭包的。因为很方便。
困惑。
22 楼 hax 2008-04-14  
不要拿removeChild来做例子。因为这涉及另一个伪内存泄漏问题。事情要一桩一桩说,不要混在一起。
21 楼 afcn0 2008-04-14  
首先函数执行没有指明宿主情况,context是host也就是window,其实closure保存的是执行环境不是什么context,context一般情况下指函数执行过程中的this指向,应该问题就是楼上上说的从DOM无法回收,但是哪个元素的handler的closure里面还握着原来DOM的指针,也就是说浏览器刷新时候的remove和用js的remove不一样
20 楼 rainshow 2008-04-14  
afcn0 写道
rainshow 写道
hax 写道
divA.onclick=function(){};

在ie6中就已经leak了。

原因是闭包造成循环引用。根源还是循环引用。

divA 通过 onclick 属性引用了 闭包,这是显而易见的。
另一方面,闭包也引用了divA,虽然闭包没有显式引用divA。

下面的两块代码中没有闭包,或者闭包引用不到divA,就不会构成循环引用,因而不leak:

divA.onclick = new Function('');


function a() {};
new function () {
  var divA;
  ...
  divA.onclick = a;
}




"闭包没有显式引用divA"即存在隐性的引用
猜测:是否包含“闭包”的函数做为一个context被闭包引用着?
猜测:对于IE来说,不管你闭包有没有实际用到这个function里的变量,function本身作为一个context都已经被引用了,相当于divA.onclick=function a(this) {} ?

不要把context和closure混为一谈,和context没关系

呵呵,我说的context跟你说的不一样吧,我这里指的是闭包需要用到的一些环境(如函数)。我不知道这个context有什么还有其他意义。
19 楼 liudaoru 2008-04-14  
<p>关于cross-page leap,下面的文章讲的就比较清楚:</p>
<p><a href='http://www.javascriptkit.com/javatutors/closuresleak/index3.shtml'>http://www.javascriptkit.com/javatutors/closuresleak/index3.shtml</a></p>
<p>泄漏的根源不是闭包,这也是当没有divA.parentNode.removeChild(divA);是不存在泄漏的原因。问题的根源在于虽然js采用的是“标记-清除”回收策略,但是js和dom各自是单独回收的,而泄漏是有存在跨js和dom的循环引用造成的。</p>
<p>针对这个问题,通过removeChild将divA这个元素从dom树中摘除,所以dom的内存回收不会涉及到它;同时由于divA是局部变量,所以也无法从global对象到达。作为局部变量的divA因为相应的dom元素仍然存在,所以存在循环引用。而最重要的是,此时通过匿名函数divA与所在的scope相关联,进而导致整个leakTest中的变量均无法回收。这也是为什么使用this以后不存在问题的原因,同样将divA设为全局变量也不会有问题的原因。</p>
<p>divA.onclick=function(){}; 有问题的原因是其中隐含了对event的引用。</p>
<p>以上是我的理解,不当之处请大家指出。</p>
<p>下面是所引用文章中比较关键的文字:</p>
<p>...</p>
<p>Since the JScript garbage collector is a <a href='http://blogs.msdn.com/ericlippert/archive/2003/09/17/53038.aspx' target='_blank'>mark and sweep GC</a>, you may think that it would handle circular references. And in fact it does. However this circular reference is between the DOM and JS worlds. DOM and JS have separate garbage collectors. Therefore they cannot clean up memory in situations like the above.</p>
<p><strong>...</strong></p>
<p>The above pattern will leak due to closure. Here the closure's global variable <code>obj</code> is referring to the <strong>DOM</strong> element. In the mean time, the <strong>DOM</strong> element holds a reference to the entire closure. This generates a circular reference between the <strong>DOM</strong> and the <strong>JS</strong> worlds. That is the cause of leakage.</p>
<p>...</p>
18 楼 KKFC 2008-04-14  
太多逃逸的变量 不好.

相关推荐

Global site tag (gtag.js) - Google Analytics