鸟食轩

 Microsoft .NET[C#] MVP 2003
随笔 - 424, 文章 - 231, 评论 - 5402, 引用 - 344
数据加载中……

理解并解决IE的内存泄漏方式[翻译3]

    这一节非常的让人郁闷,不管是翻译过程还是文章内容本身。居然对DOM的这样简单操作区别都可以带来这么大的泄漏问题,真的是没有语言了。看完后欢迎说说你的感受,我的感受附在了文尾。



页面交叉泄漏(Cross-Page Leaks)

    这种基于插入顺序而常常引起的泄漏问题,主要是由于对象创建过程中的临时对象未能被及时清理和释放造成的。它一般在动态创建页面元素,并将其添加到页面DOM中时发生。一个最简单的示例场景是我们动态创建两个对象,并创建一个子元素和父元素间的临时域(译者注:这里的域(Scope)应该是指管理元素之间层次结构关系的对象)。然后,当你将这两个父子结构元素构成的的树添加到页面DOM树中时,这两个元素将会继承页面DOM中的层次管理域对象,并泄漏之前创建的那个临时域对象。下面的图示示例了两种动态创建并添加元素到页面DOM中的方法。在第一种方法中,我们将每个子元素添加到它的直接父元素中,最后再将创建好的整棵子树添加到页面DOM中。当一些相关条件合适时,这种方法将会由于临时对象问题引起泄漏。在第二种方法中,我们自顶向下创建动态元素,并使它们被创建后立即加入到页面DOM结构中去。由于每个被加入的元素继承了页面DOM中的结构域对象,我们不需要创建任何的临时域。这是避免潜在内存泄漏发生的好方法。

    DOMInsertionOrder.gif
    Figure 3. DOM插入顺序泄漏模型

    接下来,我们将给出一个躲避了大多数泄漏检测算法的泄漏示例。因为我们实际上没有泄漏任何可见的元素,并且由于被泄漏的对象太小从而你可能根本不会注意这个问题。为了使我们的示例产生泄漏,在动态创建的元素结构中将不得不内联的包含一个脚本函数指针。在我们设置好这些元素间的相互隶属关系后这将会使我们泄漏内部临时脚本对象。由于这个泄漏很小,我们不得不将示例执行成千上万次。事实上,一个对象的泄漏只有很少的字节。在运行示例并将浏览器导航到一个空白页面,你将会看到两个版本代码在内存使用上的区别。当我们使用第一种方法,将子元素加入其父元素再将构成的子树加入页面DOM,我们的内存使用量会有微小的上升。这就是一个交叉导航泄漏,只有当我们重新启动IE进程这些泄漏的内存才会被释放。如果你使用第二种方法将父元素加入页面DOM再将子元素加入其父元素中,同样运行若干次后,你的内存使用量将不会再上升,这时你会发现你已经修复了交叉导航泄漏的问题。

<html>
    
<head>
        
<script language="JScript"></script>
    
</head>

    
<body>
        
<button onclick="LeakMemory()">Memory Leaking Insert</button>
        
<button onclick="CleanMemory()">Clean Insert</button>
        
<div id="hostElement"></div>
    
</body>
</html>

    这类泄漏应该被澄清,因为这个解决方法有悖于我们在IE中的一些有益经验。创建带有脚本对象的DOM元素,以及它们已进行的相互关联是了解这个泄漏的关键点。这实际上这对于泄漏来说是至关重要的,因为如果我们创建的DOM元素不包含任何的脚本对象,同时使用相同的方式将它们进行关联,我们是不会有任何泄漏问题的。示例中给出的第二种技巧对于关联大的子树结构可能更有效(由于在那个示例中我们一共只有两个元素,所以建立一个和页面DOM不相关的树结构并不会有什么效率问题)。第二个技巧是在创建元素的开始不关联任何的脚本对象,所以你可以安全的创建子树。当你把你的子树关联到页面DOM上后,再继续处理你需要的脚本事件。牢记并遵守关于循环引用和闭包函数的使用规则,你不会再在挂接事件时在你的代码中遇到不同的泄漏。

    我真的要指出这个问题,因为我们可以看出不是所有的内存泄漏都是可以很容易发现的。它们可能都是些微不足道的问题,但往往需要成千上万次的执行一个更小的泄漏场景才能使问题显现出来,就像DOM元素插入顺序引起的问题那样。如果你觉得使用所谓的"最佳"经验来编程,那么你就可以高枕无忧,但是这个示例让我们看到,即使是"最佳"经验似乎也可能带来泄漏。我们这里的解决方案希望能提高这些已有的好经验,或者介绍一些新经验使我们避免泄漏发生的可能。

    to be continued ...



    不得不承认仔细的弄完这节的内容后,我有点被搞糊涂了。这个所谓的什么Cross-Page Leaks,是目前IE所有内存泄漏中我认为最严重的,前面两节说道的循环引用闭包函数都是我们可以发现并合理避免的。这里先不说Cross-Page Leaks到底有多少种场景,单示例给出的这个DOM插入顺序问题就够搞死人了。因为在使用Dhtml时,使用一个GetXxx、CreateXxx或GenerateXxx函数来得到一个复杂的Html对象结构,再attach到需要的primary DOM上是再平常不过的操作了,现在连这样都要带来Leak,真的是印证了这年头连萝卜都靠不住。之前看到过一个说法,由于IE的一些窗口之间有所有(owner)关系,比如谁是谁的openner啥的,所以IE会保持一些navigate away的页面内容,以便于被别的页面引用。可是不知道IE怎么搞得,连页面跳转到了别的domain或完全的空白(about:blank)页中后,所占用的内存还是不能释放。这样严重的bug,微软还能美其名曰Cross-Page Leaks,真的是绝倒。其实只要能保证navigate to other domain或navigate to blank page后完全释放内存(这里根本都不需要GC,直接全部release就行了),其它的泄漏问题也就没有那么尖锐了,因为这时用户在不关闭IE的情况下总还有个完全清理并回收内存的机会。那种运行了很久后,IE占用2,3百M内存(PM+VM)的情况就能大大的减小。不能回收内存的另一个莫名其妙的问题是,这时脚本的引擎的效率会变得异常的低。话又说回来,现在的机器,都是512M或1G的内存,IE就是占用2,3百M内存,甚至3,4百M也不是什么不能忍受的问题。可是这个时候,脚本引擎不知道怎么搞得,运行效率变得非常的低,似乎是每次GC脚本引擎都要把那些已leak的内存完全的sweep and mark一遍,那个代价可想而知。当然这是一个猜想,否则也无法解释为什么脚本引擎的效率变得如此的低。

posted on 2006-06-17 22:00 birdshome 阅读(12290) 评论(17)  编辑 收藏 所属分类: Jscript&Dhtml开发

评论

#1楼    回复  引用  查看    

一直在关注这个翻译系列,IE内存泄露确实是个麻烦的问题。
2006-06-18 20:51 | 蔡克伦      

#2楼 [楼主]   回复  引用  查看    

之前看的不是很仔细,现在翻译后觉得越来越无所适从,也就是说在IE(目前是到IE6)上做复杂(一定是比较复杂,一个页面有个3,5千行脚本那种)的dhtml开发,就不用再去琢磨避免memory leak了,because no way.
2006-06-18 21:18 | birdshome      

#3楼    回复  引用  查看    

@birdshome
同感。应该说IE本身就不是为这种应用而设计的,微软也比较不思进取,有时候当前页面都被重定位到别的页面去了,原来泄漏的内存还是依然泄漏在那里,这种我觉得应该算是bug了。
2006-06-19 08:43 | 蔡克伦      

#4楼    回复  引用    

就是你们这帮家伙把ms惯坏了,IE6这么烂,还要坚持在上面开发应用程序!

#5楼    回复  引用  查看    

有个问题不是很清楚,就是为什么不先删除子节点而要先删除父节点:
(原文)
hostElement.removeChild(parentDiv);
parentDiv.removeChild(childDiv);

父节点都被删除了,怎么还能调用他的方法?

不解.

2006-06-22 09:27 | 畅想自由      

#6楼    回复  引用    

用innerHTML呢?
也会出现这样的问题吗?

birdsome 说到我心里去了,内存泄露倒是次要,随之而来的执行效率低下才是心腹大患。
因为很少有用户能刷页面成千上万次,泄露占用的内存和读入的图片所占内存简直是小巫见大巫。反之执行效率的低下可以让一些老机器彻底抓狂!多次刷新页面后,可以使程序的执行时间几倍几十倍的增加......!
相对firefox j执行的高效与“干净”简直有种天堂的感觉。微软干脆在firefox开发组里挖些人来得了。
2006-06-23 17:22 | yorkane [未注册用户]

#7楼    回复  引用  查看    

真是有点晕了,连插入的顺序都会导致leak,无语中......
现在Web的应用越来越多,越来越复杂,我们可咋活啊
2006-09-01 09:24 | 浪子      

#8楼    回复  引用    

to 畅想自由:
這是刪除引用關係,對象的真正刪除由 GC 決定。
執行了hostElement.removeChild(parentDiv); 後,parentDiv所指向的對象還沒有被刪除,因為還有 parentDiv 這個變量對它的引長。
2006-12-18 15:21 | LungZeno [未注册用户]

#9楼    回复  引用    

to 畅想自由:
這是刪除引用關係,對象的真正刪除由 GC 決定。
執行了hostElement.removeChild(parentDiv); 後,parentDiv所指向的對象還沒有被刪除,因為還有 parentDiv 這個變量對它的引用。
2006-12-18 15:21 | LungZeno [未注册用户]

#10楼    回复  引用    

有个办法,就是创建的元素总是先加入DOM,再退出DOM,就不会产生临时域,比如:

hide = ...// body中一个display:none的div
document.$ = document.createElement;
document.createElement = function(x){
return hide.removeChild(hide.appendChild(document.$(x)));
}

这样修改createElement后,测试前面例子LeakMemory()也不会有泄漏了
不过,多出来这两个append和remove消耗多少时间还需要测试
2007-02-21 19:59 | crazybird [未注册用户]

#11楼    回复  引用    

哈哈,发现这篇文章本身的bug了!
之所以LeakMemory()会产生泄漏,并非文章中的原因!!!
关键在于:createElement("<div onclick='foo()'>") 中的onclick上!!

改成createElement('div')就没有泄漏啦!(IE6 sp2测试过)
然后再加上parentDiv.onclick=LeakMemory这个事件绑定也没有泄漏


所以根本原因极有可能是IE对createElement('<div onclick="foo">')的处理有bug,在形成临时域时,内部对onclick事件的js函数循环引用了
解决办法就非常简单了,不要createElement使用带事件绑定的元素,应该在create之后用代码来绑定
2007-02-22 15:35 | crazybird [未注册用户]

#12楼    回复  引用    

所以关键因素还是循环引用对DOM元素回收的影响
改变元素appendChild顺序能消除泄漏 估计是 因为那样改变了临时域,消除了内部产生的循环引用

即使是用代码方式绑定事件也要注意循环引用,就像此系列翻译中其他几篇提到的那样
2007-02-22 15:48 | crazybird [未注册用户]

#13楼 [楼主]   回复  引用  查看    

@crazybird
你的实验非常棒!
2007-02-22 16:45 | birdshome      

#14楼    回复  引用    

呵呵,再细看文章,其中也提到了这个泄漏的是onclick的脚本对象,关注文章强调的创建顺序,把脚本对象的字眼给漏掉了,呵呵...
总之很容易解决,可以松口气了
2007-02-23 18:14 | crazybird [未注册用户]

#15楼    回复  引用    

一个设想:在刷新页面时,释放内存的策略可以由开发人员来定。比如提供一个方法window.setGCMethod(method), 当参数为1时,释放所有的dom对象和js对象,不管什么循环引用。


IE一刀切地做法很不好啊
2007-03-03 17:24 | xty [未注册用户]

#16楼    回复  引用    

"现在的机器,都是512M或1G的内存,IE就是占用2,3百M内存,甚至3,4百M也不是什么不能忍受的问题。"
观点是错的,

最近在多台机子上经常发生单个IEXPLORE.EXE占用1~200M的内存(虽然每台机子上都有1G以上的内存,但当达到100M以上时IE就有点瘫痪了),其表现为在页面上点击链接或在地址栏输入地址无反应,不能前进后退
只有退出所有页面,重启IEXPLORE.EXE才解决问题,开始怀疑是病毒但是又查杀不出什么问题,而且CPU也没有占用的迹象,即使刚刚装完的系统也有此问题

奇怪的是以前没发觉?

今天来到这里看了一下,不懂但好像有点关系

这算不算漏洞??
2007-03-29 23:53 | 无 [未注册用户]

#17楼    回复  引用    

有种欲哭无泪的感觉,我的机器有2G内存但是如果ie占有大量内存的时候,整个浏览器处于假死状态,超慢.这个绝对不是占不占内存的问题,
不同意4楼的说法windows装好后,只有ie一种浏览器,难到让开发人员以火狐为标准??
2008-04-12 17:35 | sunyujia [未注册用户]

标题  
姓名  
主页
Email (只有博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2006-06-30 00:17 编辑过


相关链接:

历史上的今天:
2005-06-17 获取汉字生母