鸟食轩

 Microsoft .NET[C#] MVP 2003
随笔 - 427, 文章 - 234, 评论 - 5468, 引用 - 346
数据加载中……

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

    这篇文章其实已经看了有些日子了,并且最近的一些开发都在尽量的遵循文中的原则。可是目前的情况是代码规模稍微大点以后,IE的内存泄漏还是很严重,于是我非常生气(倒没啥后果)觉得该把这篇文章挖出来批批。为了方便批斗,所以决定先给翻译成中文,结果在精读以后,发现每个泄漏情景的描述和避免,作者几乎都留了一手,所以这么看来文章又都对了,没啥可批的啦。只是让我想起啦真的刘一手。。。


Author: Justin Rogers ,Micrsoft Corporation June 2005
Translator by: http://birdshome.cnblogs.com

Web开发的发展

    在过去一些的时候,Web开发人员并没有太多的去关注内存泄露问题。那时的页面间联系大都比较简单,并主要使用不同的连接地址在同一个站点中导航,这样的设计方式是非常有利于浏览器释放资源的。即使Web页面运行中真的出现了资源泄漏,那它的影响也是非常有限而且常常是不会被人在意的。

    今天人们对Web应用有了高更的要求。一个页面很可能数小时不会发生URL跳转,并同时通过Web服务动态的更新页面内容。复杂的事件关联设计、基于对象的JScript和DHTML技术的广泛采用,使得代码的能力达到了其承受的极限。在这样的情况和改变下,弄清楚内存泄露方式变得非常的急迫,特别是过去这些问题都被传统的页面导航方法给屏蔽了。

    还算好的事情是,当你明确了希望寻找什么时,内存泄露方式是比较容易被确定的。大多数你能遇到的泄露问题我们都已经知道,你只需要少量额外的工作就会给你带来好处。虽然在一些页面中少量的小泄漏问题仍会发生,但是主要的问题还是很容易解决的。

泄露方式

    在接下来的内容中,我们会讨论内存泄露方式,并为每种方式给出示例。其中一个重要的示例是JScript中的Closure技术,另一个示例是在事件执行中使用Closures。当你熟悉本示例后,你就能找出并修改你已有的大多数内存泄漏问题,但是其它Closure相关的问题可能又会被忽视。

现在让我们来看看这些个方式都有什么:

 1、循环引用(Circular References) — IE浏览器的COM组件产生的对象实例和网页脚本引擎产生的对象实例相互引用,就会造成内存泄漏。这也是Web页面中我们遇到的最常见和主要的泄漏方式;

 2、内部函数引用(Closures) — Closures可以看成是目前引起大量问题的循环应用的一种特殊形式。由于依赖指定的关键字和语法结构,Closures调用是比较容易被我们发现的;

 3、页面交叉泄漏(Cross-Page Leaks) — 页面交叉泄漏其实是一种较小的泄漏,它通常在你浏览过程中,由于内部对象薄计引起。下面我们会讨论DOM插入顺序的问题,在那个示例中你会发现只需要改动少量的代码,我们就可以避免对象薄计对对象构建带来的影响;

 4、貌似泄漏(Pseudo-Leaks) — 这个不是真正的意义上的泄漏,不过如果你不了解它,你可能会在你的可用内存资源变得越来越少的时候极度郁闷。为了演示这个问题,我们将通过重写Script元素中的内容来引发大量内存的"泄漏"。

循环引用

    循环引用基本上是所有泄漏的始作俑者。通常情况下,脚本引擎通过垃圾收集器(GC)来处理循环引用,但是某些未知因数可能会妨碍从其环境中释放资源。对于IE来说,某些DOM对象实例的状态是脚本无法得知的。下面是它们的基本原则:

    CircularReferences.gif
    Figure 1: 基本的循环引用模型

    本模型中引起的泄漏问题基于COM的引用计数。脚本引擎对象会维持对DOM对象的引用,并在清理和释放DOM对象指针前等待所有引用的移除。在我们的示例中,我们的脚本引擎对象上有两个引用:脚本引擎作用域和DOM对象的expando属性。当终止脚本引擎时第一个引用会释放,DOM对象引用由于在等待脚本擎的释放而并不会被释放。你可能会认为检测并修复假设的这类问题会非常的容易,但事实上这样基本的的示例只是冰山一角。你可能会在30个对象链的末尾发生循环引用,这样的问题排查起来将会是一场噩梦。

    如果你仍不清楚这种泄漏方式在HTML代码里到底怎样,你可以通过一个全局脚本变量和一个DOM对象来引发并展现它。

<html>
    
<head>
        
<script language="JScript">
        
var myGlobalObject;
        
function SetupLeak()
        
{
            
// First set up the script scope to element reference
            myGlobalObject = document.getElementById("LeakedDiv");

            
// Next set up the element to script scope reference
            document.getElementById("LeakedDiv").expandoProperty = myGlobalObject;
        }


        
function BreakLeak()
        
{
            document.getElementById(
"LeakedDiv").expandoProperty = null;
        }

        
</script>
    
</head>
    
<body onload="SetupLeak()" onunload="BreakLeak()">
        
<div id="LeakedDiv"></div>
    
</body>
</html>

    你可以使用直接赋null值得方式来破坏该泄漏情形。在页面文档卸载前赋null值,将会让脚本引擎知道对象间的引用链没有了。现在它将能正常的清理引用并释放DOM对象。在这个示例中,作为Web开发员的你因该更多的了解了对象间的关系。

    作为一个基本的情形,循环引用可能还有更多不同的复杂表现。对基于对象的JScript,一个通常用法是通过封装JScript对象来扩充DOM对象。在构建过程中,你常常会把DOM对象的引用放入JScript对象中,同时在DOM对象中也存放上对新近创建的JScript对象的引用。你的这种应用模式将非常便于两个对象之间的相互访问。这是一个非常直接的循环引用问题,但是由于使用不用的语法形式可能并不会让你在意。要破环这种使用情景可能变得更加复杂,当然你同样可以使用简单的示例以便于清楚的讨论。

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

        
function Encapsulator(element)
        
{
            
// Set up our element
            this.elementReference = element;

            
// Make our circular reference
            element.expandoProperty = this;
        }


        
function SetupLeak()
        
{
            
// The leak happens all at once
            new Encapsulator(document.getElementById("LeakedDiv"));
        }


        
function BreakLeak()
        
{
            document.getElementById(
"LeakedDiv").expandoProperty = null;
        }

        
</script>
    
</head>
    
<body onload="SetupLeak()" onunload="BreakLeak()">
        
<div id="LeakedDiv"></div>
    
</body>
</html>

    更复杂的办法还有记录所有需要解除引用的对象和属性,然后在Web文档卸载的时候统一清理,但大多数时候你可能会再造成额外的泄漏情形,而并没有解决你的问题。

    to be continued ...

    // Closure我没有翻,他的表现是内部函数,并且可以访问父函数的变量,有人翻成闭包被骂啦一头包

posted on 2006-05-28 20:46 birdshome 阅读(14508) 评论(10)  编辑 收藏 网摘 所属分类: Jscript&Dhtml开发

评论

#1楼    回复  引用  查看    

很不错,应该注意
2006-05-28 21:02 | Dflying Chen      

#2楼    回复  引用  查看    

Closure到底怎么翻译呢,我也愁啊
2006-05-29 09:35 | 装配脑袋      

#3楼    回复  引用  查看    

我突然明白点了,Closure应该就是闭包,但是它不是指“他的表现是内部函数,并且可以访问父函数的变量”这件简单的事情,而是指一个函数,它可以在运行时获得所有其语法上可获得的变量的值。难道应该叫“闭包函数”?总之是描述一个运行时的函数。
2006-05-29 09:57 | 装配脑袋      

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

@装配脑袋
这个东西叫做"??函数"也似乎本身就不是很合适,虽然它使用function关键字来定义。在很多文章的叙述中比较强调的是这种内置函数的语法会创建出一个closure scope,而访问父函数的变量这能力依赖于这个scope,那么要是叫做"闭包函数"的话应该是指的父函数还是内部函数?还是Closure Scope?
2006-05-29 10:50 | birdshome      

#5楼    回复  引用  查看    

问题是,网上所有closure的定义中都有closure is a function....这样的说法。包括Lisp, C#, Ruby, Perl等等所有支持closure的语言都这样定义。
2006-05-29 13:04 | 装配脑袋      

#6楼    回复  引用  查看    

http://en.wikipedia.org/wiki/Closure_(computer_science)
2006-05-29 14:37 | neoragex2002      

#7楼    回复  引用    

不是很理解,最好有个例子详细解释一下
IE才会内存泄漏吗?同样的代码在Firefox下不会引起内存泄漏??
2006-05-31 11:15 | BaSaRa [未注册用户]

#8楼    回复  引用    

:) 走访一下
2006-05-31 21:23 | WIZZARD [未注册用户]

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

@BaSaRa
ff有没有ml我不好说,不过由于几乎没有任何人把企业应用网站首先定义在ff上运行,所以即使ff有ml也不会体现出严重的后果。
2006-06-01 09:57 | birdshome      

#10楼    回复  引用    

本人正在做一个dwr的聊天系统.同样在ie6下面出现这样的情况
情况表述 :
ie6 在打开页面(一般需要打开3到4次) ,本人机器是出现页面不能加载的情况,在另外一些人的机器,(目前大致判断是安装了,adobe acrobat 的机器,上出现内存不能读的错误,然后ie关闭,不过本人机器装的是acrobat 7.0,没有这个问题 )
以上 全部出现在ie6上面,更低版本没有测试,ie7 和firefox2.0以上版本无此问题!~~~
满互连网搜索,找寻到的一些方案基本都不能处理该问题,希望看完这个,能让我的页面不出现或者在连续打开关闭40次以上不出现这个问题就好啊!~~
2007-03-25 12:48 | 路人甲 [未注册用户]




标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2006-06-01 23:19 编辑过
Google站内搜索
[推荐职位]上海盛大网络招聘架构师

China-pub 计算机图书网上专卖店!6.5万品种 2-8折!
近千种 9-95 新二手计算图书火热销售中!
开发者征途系统新作:《设计模式——基于C#的工程化实现及扩展》

相关文章:

相关链接:

历史上的今天:
2005-05-28 x-library的问题