backup2007

导航

<2009年2月>
25262728293031
1234567
891011121314
15161718192021
22232425262728
1234567

统计

公告

@import url(http://www.blogjava.net/CuteSoft_Client/CuteEditor/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css);


常用链接

留言簿(1)

随笔分类

随笔档案

文章分类

文章档案

搜索

最新评论

阅读排行榜

评论排行榜

我所理解的IE内存泄露

我所理解的IE内存泄露

                           

最近在做一些web方面的东西,突然发现IEremoveChild一个元素并释放了其dom对象的引用之后,在任务管理器中并不能将内存释放. 这让我相当郁闷, Google一翻,各种内存泄露的说法,看的我头晕。最终还是有些收获。

测试1

 

1.不泄露!

function test2(){

       gb = document.getElementById('garbageBin');

       var i = 0;

       while(i++<10000){

              var o = document.createElement("<div onclick='foo();'>");

              gb.appendChild(o);

              gb.removeChild(o);

       }    

}

 

2 或者当即removeChild, 而是过后在另外一个函数中 gb.innerHTML=””

也不泄露。也就是,在刷新之前,可以把内存释放出来。

 

3 如果即不remove又不 gb置空, 则刷新也要不回内存.

 

4. 如果只是var o = document.createElement("<div onclick='foo();'>"); 其他什么也不做,

同样导致泄露。

 

以上测试在IE7IE6等会再说。 这告诉我们,如果你在创建元素的时候已经关联了事件处理对象,也就是内联的foo(), 那么一定要将元素挂载到页面中的dom树上, 这样你才有可能通过remove或者innerHTML=’’的方式回收内存。

 

我到现在对于内存泄露还有疑惑,到底怎样才算泄露?

是刷新页面之前可以把内存收回才算不泄露,还是刷新之后可以收回内存就算不泄露?

前辈们给出的MSDN上那个父子div插入顺序的例子, 我在IE7中跑, 似乎不像网上说的那样,结果是有泄露的那个函数,运行完刷新浏览器,内存被回收了。而第二个函数内存一直没长。 这样理解的话,应该是浏览器刷新之前就被回收(不上涨)就算不泄露吧。那么刷新之后还无法收回的算什么呢?更厉害的内存泄露?呵呵。我晕了。

 

暂且认为:如果在浏览器刷新之前, 内存可以被回收,才算不泄露。

得出:

规则1:创建的元素,如果包含内联脚本(这是这条规则的前提情况)一定要挂载在dom树上!不能先挂载到不在树上的元素!

如果不包含内联脚本对象, 则不会泄露。

如果包含了内联脚本,则记得先挂在树上~ 哈哈 也就是先把父节点挂载,然后挂载子节点到父节点.

再者说来,在FF等其他浏览器中,根本不允许
document.createElement("<div onclick='foo();'>");
这样的代码出现么!
所以按照标准的写法来做是有好处的!

var o =document.createElement(“div”)

o.onclick = foo;

这样就不会有泄露!

 

规则2  不讨论内联事件对象的情况,因为那是个特例, 来看这样的情况

function test(){

       gb = document.getElementById('garbageBin');

       var i = 0;

       while(i++<5000){

              (function(){

              var o = document.createElement("div");

              //产生循环引用!

              o.onclick = function(){

                     alert('haha')

              }

             

              gb.appendChild(o)

             

              //虽然remove了元素,由于循环引用的存在,无法回收内存

              //应当在remove之前,打破循环引用!

              //o.onclick = null; //break!

              gb.removeChild(o)

             

              })();

       }

}

注意哈,这此会导致内存泄露,就像前辈们说的那样, o.onclick = function一句产生了dom元素和function对象的循环引用。就算挂载到dom,然后remove,内存依然不能收回。(似乎能收回一些,但是仍然没有回到调用前的数值), PF使用率成上升趋势.
当然解决方法很简单, 只要在 removeChild 之前将循环打破即可. 使用o.onclick = null 即可,或者事件响应写在函数外面。这个网上有很多讲,不多说。我关注的其实不是这个,主要是, IE7removeChild到底能做些什么? 刷新页面的时候,回收的又是哪些内存?

(如果不append也不remove,则会彻底的泄露,刷新无济于事.)

我将红色的部分,也就是内部包装的function去掉。循环5000次,内存没有增长! 我做这样的猜测:内存泄露是由于循环引用没错,然而onclick 关联的那个function 属于匿名函数哈,在整个过程中只解析一次,因此无论你循环多少次,泄露的都只是跟这一个对象有关,因此泄露数量很少很少,以至于看不出。

然而包装成函数调用5000次呢?就生成了5000onclick关联的function, 泄露的内容就增大了5000倍,因此我们可以看的出来。

 

3

下面测试正常的创建元素。 先测试只创建, append到树上的情况.

//IE7下没有问题,内存不会增长,说明IE7可以动态回收不在结点树上的并且没有关//JS对象, 且没有其他对象引用它的元素.

function test2(){

       var i = 0;

       while(i++<5000){

              //(function(){

                     var o = document.createElement("<div>");

                     o.innerHTML = "AAA";

                     //加上下面两句也没问题!

                     buf.push(o);//如果后面不做处理,则不会被回收哦

                     buf.pop();   //直接pop掉,也就没有对象引用这个元素了       

 

              //})();

              //buf.pop 放到这里也完全OK,内存不会增长

       }

      

}

 

 

也就是说,定义一个dom元素,只要没有循环引用, appenddom树上也没问题。只要没有被引用到,就会被垃圾回收。

这几个例子都是创建一个就删除一个,那么如果先缓存下来,然后再删除呢?

看这个例子:

       var i = 0;

       while(i++<5000){

             

              (function(){

                     var o = document.createElement("<div>");

                     o.onclick = foo;

                     o.innerHTML = "AAA";

                    

                     buf.push(o);//如果后面不做处理,则不会被回收哦

              })();

       }

 

       //直接清空buf

       buf.length = 0;

       /*

       //尝试逐个置空方法

       for(var i=0;i<buf.length;i++){

              //document.body.removeChild(buf[i]);

              buf[i] = null;

       }

       */

       //尝试pop

       //while(buf.pop());

该例子先将5000个对象存在buf里,然后再尝试各种方法去释放他们,也就是断开对他们的引用。 然而实验结果是: 占用较大的内存,而且每次调用这个函数,PF使用率都是相同的! 这说明,内存不会累计增加。 然而也不会立即的释放。虽然PF使用率数值没变,但是由于再次调用还是占用这些,说明之前占用的内存被新的内容覆盖了。然而总体情况,还是没有得到理想的释放效果。 很让人郁闷,为什么创建一个就释放一个就可以,创建多个然后再释放就不完全了呢?

然而这种情况只是在测试,毕竟没人会去创建一堆不挂载到页面dom的元素吧~

 

再来看挂载到dom树上的情况:

下面的例子也OK

while(i++<5000){         

              (function(){

                     var o = document.createElement("<div>");

                     o.onclick = foo; //因为定义在了外部,没有循环引用问题

                     o.innerHTML = "AAA";

                     document.body.appendChild(o);

                     document.body.removeChild(o);

              })();

}

 

如果去掉removeChild一行,内存情况也尚好,只是增加一点,这是正常的,毕竟显示到页面上需要占用内存。 然而让我不解的是: 为什么append到页面就占用很少内存,而保存

buf里面就会占用很大内存呢??不解!
而且,如果先append,然后保存到buf里,占用内存依然很少!
还有如果不设置innerHTML, 则占用内存极少! 里面的原因我就猜不到了.

我只能得出这样的结论: IE中,创建一个元素,务必把它挂载到dom树上! 这样即使不remove,刷新后内存也会释放。

 

如果将删除代码移到function外面, 情况一样,没有泄露。

 

再来实验将5000个对象挂载后,然后再删除的情况

我们通过buf保存对象的引用:

       while(i++<10000){

              var o;

              (function(){

                     o = document.createElement("<div>");

                     o.onclick = foo;

                     o.innerHTML = "haha";

                     o.style.left="10px";

                     document.body.appendChild(o);

                     buf.push(o);//保存引用

              })();

       }

       // 这里调用removeChild来释放

       while(buf.length>0){

              document.body.removeChild(buf.pop());

       }

测试结果还不错,虽然内存会上升,但是多次调用会维持在一个水平上,这说明,每次调用时,新的对象占用旧的对象的内存,因此不会累计增加,还算OK. 

结论是:同未挂载的情况类似,如果一次性缓存多个对象然后统一removeChild来清除,则IE不会立即释放内存,如果有新的变量或者对象出现,则会覆盖那部分内存。总的情况还不差。

 

使用另一种方式删除:定义一个看不见的元素gb当做垃圾站

       /*

       while(buf.length>0){

              //首先说明,一个元素只能挂载到一个点上,因为dom是一个树结构,

              //元素对象只是将指针挂载到树的某个位置,之前对象在body,现在挂载到了

              //gb,那么body中就不显示了,而转到gb上来,

              //另外如果将一个元素innerHTML置空,意味着其子元素的内存被释放!

              //所以,这是一个名副其实的回收站~哈哈

              gb.appendChild(buf.pop());

              gb.innerHTML = "";

             

       }

       */

 

这种情况得到的结果和上面类似,然而内存占用更少,上面是35M左右,而这个维持在25M左右,情况好了不少。 只是效率低了一点,因为有append操作

其实如果不做清除的时候,内存占用到了25M. 而清除之后,第一种方法35M,第二种方法25M, 似乎根本没有清除,其实还是这样,新的内容会覆盖旧的内存,只是任务管理器没有显示出来。因为如果不清除,多次调用后内存是累计增加的,而清除后会维持在一个水平。

 

还有pop的速度很慢,改成for会快很多!

var l=buf.length;

       for(var i=0;i<l;i++){

              gb.appendChild(buf[i]);

       }

       gb.innerHTML = ""; //置空,子元素全被释放!

       buf.length = 0;        //重置buf

 

所以,使用这种方法清除元素是再好不过的了。 可是需要注意的是,如果内部元素有循环引用的现象,清除之前一定要先把循环引用断开,方法就是递归的清除类型为function的属性。

 

只要有善于发现循环引用的良好习惯~ 问题就不是问题了~

posted on 2009-02-12 04:40 backup2007 阅读(1856) 评论(0)  编辑  收藏 所属分类: 随手写写


只有注册用户登录后才能发表评论。


网站导航: