IE内存泄漏的问题

 

引起IE内存泄漏的主要情况为js对象实例跟dom对象的相互引用、Closures以及DOM插入顺序泄漏和页面交叉泄漏。这里重点对Closures以及DOM插入顺序泄漏进行说明。

dom插入顺序泄漏

创建带有脚本对象的DOM元素,以及它们已进行的相互关联是了解这个泄漏的关键点。这实际上这对于泄漏来说是至关重要的,因为如果我们创建的DOM元素不包含任何的脚本对象,同时使用相同的方式将它们进行关联,我们是不会有任何泄漏问题的。示例中给出的第二种技巧对于关联大的子树结构可能更有效(由于在那个示例中我们一共只有两个元素,所以建立一个和页面DOM不相关的树结构并不会有什么效率问题)。第二个技巧是在创建元素的开始不关联任何的脚本对象,所以你可以安全的创建子树。当你把你的子树关联到页面DOM上后,再继续处理你需要的脚本事件。牢记并遵守关于循环引用和闭包函数的使用规则,你不会再在挂接事件时在你的代码中遇到不同的泄漏。
这个所谓的什么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一遍,那个代价可想而知。当然这是一个猜想,否则也无法解释为什么脚本引擎的效率变得如此的低。

闭包函数(Closures)

由于闭包函数会使程序员在不知不觉中创建出循环引用,所以它对资源泄漏常常有着不可推卸的责任。而在闭包函数自己被释放前,我们很难判断父函数的参数以及它的局部变量是否能被释放。实际上闭包函数的使用已经很普通,以致人们频繁的遇到这类问题时我们却束手无策。在详细了解了闭包背后的问题和一些特殊的闭包泄漏示例后,我们将结合循环引用的图示找到闭包的所在,并找出这些不受欢迎的引用来至何处。

闭包函数引起的循环引用

普通的循环引用,是两个不可探知的对象相互引用造成的,但是闭包却不同。代替直接造成引用,闭包函数则取而代之从其父函数作用域中引入信息。通常,函数的局部变量和参数只能在该被调函数自身的生命周期里使用。当存在闭包函数后,这些变量和参数的引用会和闭包函数一起存在,但由于闭包函数可以超越其父函数的生命周期而存在,所以父函数中的局部变量和参数也仍然能被访问。在下面的示例中,参数1将在函数调用终止时正常被释放。当我们加入了一个闭包函数后,一个额外的引用产生,并且这个引用在闭包函数释放前都不会被释放。如果你碰巧将闭包函数放入了事件之中,那么你不得不手动从那个事件中将其移出。如果你把闭包函数作为了一个expando属性,那么你也需要通过置null将其清除。

同时闭包会在每次调用中创建,也就是说当你调用包含闭包的函数两次,你将得到两个独立的闭包,而且每个闭包都分别拥有对参数的引用。由于这些显而易见的因素,闭包确实非常用以带来泄漏。下面的示例将展示使用闭包的主要泄漏因素:
code1.png
如果你对怎么避免这类泄漏感到疑惑,我将告诉你处理它并不像处理普通循环引用那么简单。”闭包”被看作函数作用域中的一个临时对象。一旦函数执行退出,你将失去对闭包本身的引用,那么你将怎样去调用detachEvent方法来清除引用呢?在Scott Isaacs的MSN Spaces上有一种解决这个问题的有趣方法。这个方法使用一个额外的引用(原文叫second closure,可是这个示例里致始致终只有一个closure)协助window对象执行onUnload事件,由于这个额外的引用和闭包的引用存在于同一个对象域中,于是我们可以借助它来释放事件引用,从而完成引用移除。为了简单起见我们将闭包的引用暂存在一个expando属性中,下面的示例将向你演示释放事件引用和清除expando属性。

code2ie.png
在这篇KB文章中,实际上建议我们除非迫不得已尽量不要创建使用闭包。文章中的示例,给我们演示了非闭包的事件引用方式,即把闭包函数放到页面的全局作用域中。当闭包函数成为普通函数后,它将不再继承其父函数的参数和局部变量,所以我们也就不用担心基于闭包的循环引用了。在非必要的时候不使用闭包这样的编程方式可以尽量使我们的代码避免这样的问题。

最后,脚本引擎开发组的Eric Lippert,给我们带来了一篇关于闭包使用通俗易懂的好文章。他的最终建议也是希望在真正必要的时候才使用闭包函数。虽然他的文章没有提及闭包会使用的真正场景,但是这儿已有的大量示例非常有助于大家起步。

不得不说Eric Lippert同志疾呼的:Don’t use closures unless you really need closure semantics. In most cases, non-nested functions are the right way to go. 是非常消极的应付之辞。今天关于IE内存泄漏的文章已有很多,而且很大部分就是微软的自己人在解释,甚至象Eric Lippert这样引擎开发组的成员。但是他们的文章始终没有正面承认其实这就是IE的bug,而且是非常严重的bug,这事情其实完全不关脚本引擎对象和DOM对象的事。就是微软对产品不负责任的表现,不说IE4,1997那个春天那是太遥远了点,但是IE6也是2001年随xp发布的。使用COM可以给他们的开发带来很多便利,当然也利用很多现成的东西,可是居然在带来这样的严重问题后,他们却把大部分责任归咎于不合理和不正确的使用Closures技术!对于循环引用产生Memory Leak我根本就是不好意思提了,那样的问题是应该发生的吗?那就让我们拭目以待看IE7怎么解决这堆shit问题吧。

另外:在使用Array的时候,本人发现,就是将arrayObj=null以后,内存依然无法释放。
function ArrayTest()
{
var myArr=new Array();
for(var i=0;i<100000;i++)
{
var arr=new Array();
arr.push(Math.random());
myArr.push(arr);//换成myArr.push(1);也不能释放
}
myArr=null;
}

关于内存回收,windows对象dispose方法有如下解释:切断DHTML对象实例和脚本对象实例的引用链;清除全局cache变量中的数据,使用delete关键字;使用attachEvent方式导入的事件处理函数,需要detach;其它事件处理回调,使用赋null的方式清空;切断脚本对象之间的parent或child关系引用链。

还有就是尽量不要使用IE透明滤镜,我发现当插入一个长宽各为1000px并应用透明滤镜的div时,内存占用增加大约4MB。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值