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

来源:互联网 发布:windows jenkins git 编辑:程序博客网 时间:2024/05/17 23:07


在ie的内存泄露中跨页面的泄露是最严重的,浏览器刷新了仍然无法释放掉泄露占用的资源,造成访问速度越来越慢,内存占用越来越大
closure引起cross page leak的主要原因是closure和dom元素的互相引用
看这个例子:
Java代码  收藏代码

   1. <div id="bb"><div id="aa">cc</div></div> 
   2. <script type="text/javascript"> 
   3. function leakTest(){ 
   4.     var a=[];//用来加大闭包资源占用,方便观察 
   5.     for(var i=0;i<100000;i++){ 
   6.      a.push('a'); 
   7.     } 
   8.     var divA=document.getElementById('aa'); 
   9.     divA.kk=function(){}; 
  10.     divA.parentNode.removeChild(divA); 
  11. } 
  12. leakTest(); 
  13. </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');
    }
    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树中的节点不会强行释放,所以造成了跨页面泄露,这是我的个人推测,有别的意见欢迎讨论
怎么解决这个问题呢,其实我们只要打断引用链就行了
Java代码  收藏代码

   1. <div id="bb"><div id="aa">cc</div></div> 
   2. <script type="text/javascript"> 
   3. function leakTest(){ 
   4.     var a=[]; 
   5.     for(var i=0;i<100000;i++){ 
   6.      a.push('a'); 
   7.     } 
   8.     var divA=document.getElementById('aa'); 
   9.     divA.kk=function(){}; 
  10.     divA.parentNode.removeChild(divA); 
  11.     divA=null; 
  12. } 
  13. leakTest(); 
  14. </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');
    }
    var divA=document.getElementById('aa');
    divA.kk=function(){};
    divA.parentNode.removeChild(divA);
    divA=null;
}
leakTest();
</script>


或者
Java代码  收藏代码

   1. <div id="bb"><div id="aa">cc</div></div> 
   2. <script type="text/javascript"> 
   3. function leakTest(){ 
   4.     var a=[]; 
   5.     for(var i=0;i<100000;i++){ 
   6.      a.push('a'); 
   7.     } 
   8.     document.getElementById('aa').kk=function(){}; 
   9.     document.getElementById('aa').parentNode.removeChild(document.getElementById('aa')); 
  10.     //这个例子不保存aa的应用,也不会引起泄露 
  11. } 
  12. leakTest(); 
  13. </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
Java代码  收藏代码

   1. <div id="bb"><div id="aa">cc</div></div> 
   2. <script type="text/javascript"> 
   3. function leakTest(){ 
   4.     var a=[]; 
   5.     for(var i=0;i<100000;i++){ 
   6.      a.push('a'); 
   7.     } 
   8.     var divA=document.getElementById('aa'); 
   9.     divA.kk=function(){}; 
  10.     divA.parentNode.removeChild(divA); 
  11.     return divA; 
  12. } 
  13. var divA=leakTest(); 
  14. divA.kk=null; //这个可以看到内存占用比上面少了7MB,因为解除了对闭包内部函数的引用,闭包占用的资源被释放了 
  15. </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');
    }
    var divA=document.getElementById('aa');
    divA.kk=function(){};
    divA.parentNode.removeChild(divA);
    return divA;
}
var divA=leakTest();
divA.kk=null; //这个可以看到内存占用比上面少了7MB,因为解除了对闭包内部函数的引用,闭包占用的资源被释放了
</script>


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

关于cross-page leap,下面的文章讲的就比较清楚:

http://www.javascriptkit.com/javatutors/closuresleak/index3.shtml

泄漏的根源不是闭包,这也是当没有divA.parentNode.removeChild(divA);是不存在泄漏的原因。问题的根源在于虽然js采用的是“标记-清除”回收策略,但是js和dom各自是单独回收的,而泄漏是有存在跨js和dom的循环引用造成的。

针对这个问题,通过removeChild将divA这个元素从dom树中摘除,所以dom的内存回收不会涉及到它;同时由于divA是局部变量,所以也无法从global对象到达。作为局部变量的divA因为相应的dom元素仍然存在,所以存在循环引用。而最重要的是,此时通过匿名函数divA与所在的scope相关联,进而导致整个leakTest中的变量均无法回收。这也是为什么使用this以后不存在问题的原因,同样将divA设为全局变量也不会有问题的原因。

divA.onclick=function(){}; 有问题的原因是其中隐含了对event的引用。

以上是我的理解,不当之处请大家指出。

下面是所引用文章中比较关键的文字:

...

Since the JScript garbage collector is a mark and sweep GC, 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.

...

The above pattern will leak due to closure. Here the closure's global variable obj is referring to the DOM element. In the mean time, the DOM element holds a reference to the entire closure. This generates a circular reference between the DOM and the JS worlds. That is the cause of leakage.

原创粉丝点击