浏览器内存泄漏

1 什么是内存泄漏?

内存泄漏是指内存资源得不到释放 && 失去对该内存区的指针 => 无法复用内存资源,最终导致内存溢出。

2 JS中可以操作的对象有哪些?

Script中我们能操作的对象可分为三种:JS EngineObject、DOM Element 和 BOM Element。

JSEngine Object:

var obj = Object(); var array = [];等等

DOMElement:

var el = document.createElement('div');

var div = document.getElementById('name');

等等

BOMElement:

window;

window.location;

等等

其中只有JS Engine ObjectDOM Element是我们可以CRUD的,因此也就有可能发生内存泄漏的问题。

3 JS Engine Object & DOM Element的内存回收机制

JSEngine Object 的回收机制:

IE的JScriptGarbage Collector采用的是Mark-and-Sweep算法,当执行垃圾回收时会先遍历所有JS Engine Object并标记未被引用的对象,然后释放掉被标记的内存空间。由于Mark-and-Sweep算法的缘故,也能很好地释放引用孤岛的内存空间。而IE下独有的CollectGarbage()则用于回收无引用或引用孤岛的JS Engine Object。

 

DOMElement的内存回收机制:

当DOM Element不再被引用时会被回收。

 

4 两种泄漏方式

Ø  当前页面泄漏:刷新页面或跳转到其他页面就能释放的内存资源。

Ø  跨页面泄漏:刷新页面或跳转到其他页面也无法释放的内存资源。

4.5 四种泄漏模式

Justin Rogers总结出来的4种会引起泄漏的反模式:

Ø  Circular References(导致跨页面内存泄漏):

循环引用可谓是引起内存泄漏的根本原因,其他的泄漏模式最底层还是因为出现的循环引用。一般来说,脚本引擎通过它们的垃圾收集器处理循环引用,但是一些未知因素会阻止触发它们恰当地工作。在IE中的未知因素是部分脚本访问的一些DOM元素状态。

这个模式引起泄漏的原因是由于COM引用计数。脚本引擎对象(译者注:指obj1)将会持有一个到DOM元素的引用,在清理并释放DOM元素指针之前,会等待所有外部引用被删除。在我们的例子中,我们在脚本引擎中有两个引用:脚本引擎作用域和DOM元素的自定义属性。当终止时,脚本引擎将会释放第一个引用,而DOM元素引用永远不会被释放,因为它在等待脚本引擎对象(译者注:指obj1)释放它!你可能会想,很容易检测并修正此问题,但是在实际的应用中这种基本情况只不过是冰山一角。你可能在一个有30个对象链条的尾端获得一个循环引用,这种情况很难检测。


一个全局脚本引擎变量和一个DOM元素构造一次内存泄漏的例子:


或者:


注:

为了打破泄漏模式,你可以显式指定一个null值。通过在document卸载之前指定null值,你就是在告诉脚本引擎,在元素和引擎中的对象之间不再存在任何关联。现在它就可以正确地清理引用并释放DOM元素。这种情况下,你作为Web开发者,对你的对象之间的关系所知道的比脚本引擎更多。

内存不会泄漏的例子:


Ø  Closures(闭包泄漏,导致跨页面内存泄漏):

闭包具有Lexical scope特性,延长了方法参数和局部变量的生命周期,但同时又容易在无意当中引入循环引用的问题。如下图所示:


在普通的循环引用中,有两个固定的对象互相持有对方的引用,但是闭包的情况不同。与直接创建引用不同的是,它们通过从父函数作用域中导入信息而被创建。一般情况下,当调用函数时,一个函数的局部变量和所使用的参数仅仅在函数自身的生命周期中存在。在使用闭包时,这些变量和参数会继续会被外部引用,只要闭包还处于活动状态,既然闭包可以在它们父函数的生命周期之外存活,那么那个函数中的任何变量和参数也可以。在示例中,Parameter 1在函数调用结束之后被正常地释放。因为我们添加了一个闭包,第二个引用被创建,并且第二个引用不会被释放,直到闭包自身被释放掉。如果你恰好把这个闭包绑定到一个事件上,那么你就必须把它从事件上删除。如果你恰好把这个闭包设置到一个自定义属性上,那么你就必须把此自定义属性设置为null。

每次调用都会创建闭包,所以,调用这个函数两次将会创建两个独立的闭包,每个都持用那次所传递进来的参数引用。由于这个显然的特性,闭包非常容易引起泄漏。内存泄漏和不泄漏的例子:

Ø  Cross-page Leaks(当前页面内存泄漏):

由于节点建立联系时会寻找scope,若没有则创建temporaryscope,若有则抛弃原有的temporary scope采用已有的scope,如下图所示:


内存泄漏和不泄漏的例子:


注:

childDivparentDiv建立连接时,为让childDiv能获取parentDiv的信息,IE会创建temporary scope。而当将parentDiv添加到DOM tree中时,则childDivparentDiv均继承documentscope,而temporary scope却不会被GC释放,而要等待浏览器刷新页面才能清理。


注:

一直使用document scope,不会创建temporary scope

Ø  Pseduo-Leaks[秀逗模式(假泄漏)]:

很多时候,某些API的实际表现和期望的表现可能会导致你误判内存泄漏。假泄漏几乎总是出现在同一个页面的动态脚本操作中,并且在从一个页面导航到一个空页面之后变得很少可观察到。这就是你如何消除跨页面泄漏问题的方法,然后开始观察内存用量是否符合预期。我们将会用使用脚本文本重写作为秀逗模式的例子。

和DOM插入顺序问题一样,此问题也依赖创建临时对象而泄漏内存。通过一次又一次地重写一个脚本元素中的脚本文本,慢慢地你开始泄漏大量脚本引擎对象,它们是被插入在原先的内容中的。特别地,与调试脚本相关的对象会被抛弃,就像完全形成的脚本元素。连续创建多个JS Engine Object,而GC未能及时释放内存,其实根本就不是内存泄漏。例如:


如果你运行以上代码并再次使用任务管理器技巧,当在“泄漏页面”和空白页面进行导航时,你不会注意到脚本泄漏。这种脚本泄漏完全处于一个页面中,并且当你导航出去时你又会拿回你的内存。这个例子之所以有毛病的原因是由于期望的行为。你期望在重写一些脚本之后原先的脚本不会保留下来。但实际上它保留下来了,因为它可能已经用来绑定事件,并且可能存在外部引用计数。像你能看到的一样,这是一种秀逗模式。表面上看,内存使用量看起来非常糟糕,但是却有一个完全正确的理由。

 

关于预防泄漏的建议:

每一个Web开发者都创建了一个个人的代码案例列表,当他们在代码中看到它们的时候,他们知道存在泄漏并且设法绕过这些泄漏。这非常方便,并且也是如今的 Web相对无泄漏的原因。按照模式的方法考虑泄漏,替代代码案例,你可以开始发展更好的战略来处理它们。方法是在设计阶段就把它们考虑进来,并且确定你有 对付任何潜在泄漏的计划。使用防御式编码技巧,并且假定你必须清理你自己的所有内存。虽然这是对此问题的夸大,你很少必须清理自己的内存;哪些变量和自定 义属性存在潜在泄漏就变得显而易见。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值