在 IE 中,内存泄漏

分享于 

18分钟阅读

Web开发

  繁體

介绍

如果你开发客户端可用脚本对象,迟早你会发现自己发现了内存泄漏。 可以能你的浏览器会像海绵一样吸引内存,你几乎不能在访问网站之后找到一个原因。

微软开发者Justing在他的优秀文章中描述了 IE 泄漏模式

在本文中,我们将从一个稍微不同的角度来回顾这些模式,并支持图表和内存利用。 我们还将介绍几个微妙的泄漏场景。 首先,如果你还没有阅读那篇文章,我强烈建议你阅读那篇文章。

为什么内存泄漏?

内存泄漏的问题不仅限于 IE。 几乎任何浏览器如果你提供了足够的条件( 这样做并不难,我们很快就会看到),( 包括但不限于 Mozilla,Netscape和 Opera ) 将泄漏内存。 但( 我认为 ymmv 等等 ) IE 是leakers的王者。

别误会我,我不属于那些叫"IE 有内存泄漏,检查这个新工具 [link-to-tool],看看你自己"的人。 让我们来讨论一下 IE 是多么糟糕,并且覆盖了它的他浏览器中的所有缺陷"。

每个浏览器都有自己的优缺点。 例如,在初始引导时,Mozilla占用太多内存,在字符串和 array 操作中,Opera 可能会崩溃。

如果你读过我以前的文章,那么你就已经知道我是可用性,易访问性和标准的裂缝了。 Opera 但这并不意味着我要对 IE 进行圣战。 我想要在这里跟踪一个分析路径,并检查不同的泄漏模式,看起来不是很明显。

虽然我们将关注 IE 中的内存泄漏情况,但这里讨论同样适用于其他浏览器。

一个简单的开始

让我们从一个简单的例子开始:

[Exhibit 1 - Memory leaking insert due to inline script]
<html>
<head>
<script type="text/javascript">
 function LeakMemory(){
 var parentDiv = 
 document.createElement("<div onclick='foo()'>");
 parentDiv.bigString = new Array(1000).join(
 new Array(2000).join("XXXXX"));
 }
</script></head><body>
<input type="button" 
 value="Memory Leaking Insert" onclick="LeakMemory()"/></body></html>

第一个任务 返回指定索引处的元素的索引。 将创建一个 div 元素,并在脚本对象驻留的位置创建临时作用域。 第二个赋值 parentDiv.bigString=... 将一个大对象附加到 parentDiv。 当 LeakMemory() 方法被调用时,将在该函数的范围内创建一个DOM元素,一个非常大的对象。

运行示例并点击该按钮后,你的内存图可能会如下所示:

增加频率

没有可以见泄漏啊如果我们做几百次而不是二十次或者几千次? 会不会是同一个? 以下代码反复调用作业来完成这里目标:

[Exhibit 2 - Memory leaking insert (frequency increased) ]
<html>
<head>
<script type="text/javascript">
 function LeakMemory(){
 for(i = 0; i <5000; i++){
 var parentDiv = 
 document.createElement("<div onClick='foo()'>");
 }
 }
</script></head><body>
<input type="button" 
 value="Memory Leaking Insert" onclick="LeakMemory()"/></body></html>

下面是相应的图:

内存使用中的斜坡表明内存泄漏。 斜坡末端的水平线( 最后 20秒) 是刷新页面后加载另一个( 关于:空白) 页面的内存。 这表明泄漏是实际泄漏,而不是伪泄漏。 除非关闭浏览器窗口和其他依赖的Windows,否则将不会回收内存。

假设你有十几页有类似的泄漏图。 数小时后,你可能希望重新启动浏览器( 甚至你的电脑),因为它停止响应。 淘气的浏览器吞噬你所有的资源。 但是这是一个极端的情况,因为 Windows 会在内存消耗达到一定级别时增加虚拟内存大小。

这不是一个很好的方案。 如果他们在产品 showcase/training/demo. 中发现这种情况,你的客户/老板将不会非常高兴。

仔细的眼睛可能会发现第二个例子中没有 bigString。 这意味着泄漏仅仅是内部脚本对象( 例如。 匿名脚本 onclick='foo()' )。 这里脚本未被正确释放。 这在每次迭代时导致内存泄漏。 为了证明我们的论文,我们运行一个稍微不同的测试用例:

[Exhibit 3 - Leak test without inline script attached]
<html>
<head>
<script type="text/javascript">
 function LeakMemory(){
 for(i = 0; i <50000; i++){
 var parentDiv = 
 document.createElement("div");
 }
 }
</script></head><body>
<input type="button" 
 value="Memory Leaking Insert" onclick="LeakMemory()"/></body></html>

下面是相应的内存图:

正如你所看到的,我们已经完成了,个迭代而不是 5000,而仍然是内存使用量( 例如。 没有泄漏。轻微的斜坡是由于我电脑中的其他过程造成的。

让我们更改我们的代码,以更标准和稍微不一样的方式( 这里的词不正确,但找不到更好的词) 没有嵌入的inline 脚本。

介绍闭包

下面是另一段代码。 我们不附加脚本 inline,而是在外部附加它:

[Exhibit 4 - Leak test with a closure]
<html>
<head>
<script type="text/javascript">
 function LeakMemory(){
 var parentDiv = document.createElement("div");
 parentDiv.onclick=function(){
 foo();
 };
 parentDiv.bigString = 
 new Array(1000).join(new Array(2000).join("XXXXX"));
 }
</script></head><body>
<input type="button" 
 value="Memory Leaking Insert" onclick="LeakMemory()"/></body></html>

如果你不知道闭包是什么,那么在网站上有很好的引用,你可能会发现。 闭包是非常有用的模式;你应该学习它们并将它们保存在你的知识库中。

下面是显示内存泄漏的图。 这与以前的例子有些不同。 分配给 parentDiv.onclick的匿名函数是关闭 parentDiv的闭包,它在JS和DOM之间创建循环引用,并创建一个众所周知的内存泄漏问题:

在 上面 场景中生成泄漏,我们应该单击按钮,刷新页面,再次单击按钮,刷新页面等等。

单击按钮而不进行后续刷新,只会产生一次泄漏。 因为每次单击时,parentDivonclick 事件被重新分配,并且对先前闭包的循环引用被破坏。 因此在每个页面加载时,由于循环引用,只有一个不能被垃圾收集的闭包。 其余的被成功清除。

更多泄漏模式

本文详细介绍了 below 中显示的所有模式。 我只是为了完整而浏览它们:

[Exhibit 5 - Circular reference because of expando property]
<html>
<head>
<script type="text/javascript">
 var myGlobalObject;
 function SetupLeak(){
 //Here a reference created from the JS World //to the DOM world. myGlobalObject=document.getElementById("LeakedDiv");
 //Here DOM refers back to JS World; //hence a circular reference.//The memory will leak if not handled properly. document.getElementById("LeakedDiv").expandoProperty=
 myGlobalObject;
 }
</script></head><body onload="SetupLeak()">
<div id="LeakedDiv"></div></body></html>

全局变量 myGlobalObject 指的是DOM元素 LeakDiv ;同时 LeakDiv 通过它的expandoProperty 引用全局对象。 情况如下所示:

由于在DOM节点和JS元素之间创建循环引用,上面 Pattern 将泄漏。

JScript垃圾回收器是标记和清理垃圾回收器,因此可能会认为它会处理循环引用。 事实上它确实。 然而这个循环引用在DOM和JS的世界之间。 DOM和JS有独立的垃圾收集器。 因此,他们不能清理像上面这样的情况下的内存。

创建循环引用的另一种方法是将DOM元素封装为全局对象的属性:

[Exhibit 6 - Circular reference using an Encapsulator pattern]
<html>
<head>
<script type="text/javascript">function Encapsulator(element){
 //Assign our memeberthis.elementReference = element;
 // Makea circular reference element.expandoProperty = this;
}function SetupLeak() {
 //This leaksnew Encapsulator(document.getElementById("LeakedDiv"));
}
</script></head><body onload="SetupLeak()">
<div id="LeakedDiv"></div></body></html>

以下是它的外观:

但是,闭包在DOM节点上最常见的用法是事件附件。 下面的代码将泄漏:

[Exhibit 7 - Adding an event listener as a closure function]
<html>
<head>
<script type="text/javascript">
window.onload=function(){
 // obj will be gc'ed as soon as // it goes out of scope therefore no leak.var obj = document.getElementById("element");
 // this creates a closure over"element"// and will leak if not handled properly. obj.onclick=function(evt){
. . . logic.. .
 };
};
</script></head><body>
<div id="element"></div></body></html>

下面是描述闭包的图表,它在DOM世界和JS世界之间创建循环引用。

由于关闭,上面 Pattern 将泄漏。 这里的闭包变量 obj的全局引用是DOM元素。 同时,DOM元素持有对整个闭包的引用。 这将在DOM和JS世界之间生成循环引用。 这就是泄漏的原因。

当我们删除关闭时,我们看到泄漏已经消失了:

[Exhibit 8- Leak free event registration - No closures were harmed]
<html>
<head>
<script type="text/javascript">
window.onload=function(){
 // obj will be gc'ed as soon as // it goes out of scope therefore no leak.var obj = document.getElementById("element");
 obj.onclick=element_click;
};//HTML DOM object"element" refers to this function//externallyfunction element_click(evt){
. . . logic.. .
}
</script></head><body>
<div id="element"></div></body></html>

下面是 上面 代码部分的图表:

这里 Pattern 将不会泄漏,因为函数 window.onload 完成执行时,JS对象 obj 将标记为垃圾 Collection。 这样就不会有对JS端的DOM节点的引用了。

最后但不是最小的泄漏 Pattern 是"跨页泄漏":

[Exhibit 10 - Cross Page Leak]
<html>
<head>
<script type="text/javascript">function LeakMemory(){
 var hostElement = document.getElementById("hostElement");
 // Do it a lot, look at Task Manager for memory responsefor(i = 0; i <5000; i++){
 var parentDiv =
 document.createElement("<div onClick='foo()'>");
 var childDiv =
 document.createElement("<div onClick='foo()'>");
 // This will leak a temporary object parentDiv.appendChild(childDiv);
 hostElement.appendChild(parentDiv);
 hostElement.removeChild(parentDiv);
 parentDiv.removeChild(childDiv);
 parentDiv = null;
 childDiv = null;
 }
 hostElement = null;
}
</script></head><body>
<input type="button" 
 value="Memory Leaking Insert" onclick="LeakMemory()"/>
<div id="hostElement"></div></body></html>

由于我们在展示 1时发现内存泄漏,所以这种 Pattern 泄漏并不奇怪。 下面是这样:将 childDiv 附加到 parentDiv 时,将创建从 childDivparentDiv的临时范围,它会泄漏临时脚本对象。 注意 document.createElement(" <div onClick='foo()'>") ; 是一种非标准的事件附件方法。

只是使用"最佳实践"的( 就像nmo在他的文章中提到的) 不够。 你也应该尽可能地坚持标准。 如果没有,他可能对几个小时前工作正常的代码有什么错误的线索。

无论如何,我们来订购。 代码 below 不会泄漏:

[Exhibit 11 - DOM insertion re-ordered - no leaks]
<html>
<head>
<script type="text/javascript">function LeakMemory(){
 var hostElement = document.getElementById("hostElement");
 // Do it a lot, look at Task Manager for memory responsefor(i = 0; i <5000; i++){
 var parentDiv =
 document.createElement("<div onClick='foo()'>");
 var childDiv =
 document.createElement("<div onClick='foo()'>");
 hostElement.appendChild(parentDiv);
 parentDiv.appendChild(childDiv);
 parentDiv.removeChild(childDiv);
 hostElement.removeChild(parentDiv);
 parentDiv = null;
 childDiv = null;
 }
 hostElement = null;
}
</script></head><body>
<input type="button" 
 value="Memory Leaking Insert" onclick="LeakMemory()"/>
<div id="hostElement"></div></body></html>

我们应该记住,尽管它是市场领先者,但 IE 并不是世界上唯一的浏览器。 编写特定的非标准代码是编写代码的糟糕做法。 计数器参数也是 true。 我的意思是"mozilla是最好的浏览器,所以我编写mozila特定代码;我不关心它到底发生了什么"是一个错误的姿态。 你应该尽可能扩大你的频谱。 因此,只要可能,你应该尽可能地将兼容的代码编写到最高的范围。

编写,"向后兼容"代码现在是"输出"。 "in"编写"向前兼容"( 也称为标准兼容) 代码,将在今后和未来的浏览器中运行。

结束语

本文的目的是表明并非所有的泄漏模式都很容易找到。 你可以能几乎不会注意到它的中一些,可以能是由于几千次迭代后的小记帐物件。 了解进程的内部是成功的不可否认的关键。 请注意泄漏模式。 不要以暴力的方式调试你的应用程序,看看代码 Fragments,检查是否有与你的系统相匹配的Pattern。

编写防御代码并处理所有可能的泄漏问题并不是过度优化。 为了简单起见,漏洞proofness不是开发人员可以能选择实现或者不取决于他的情绪的特性。 这是创建稳定。一致和向前兼容代码的要求。 每个网站开发者都应该知道。 对不起,对于JS闭包和DOM对象之间的泄漏问题,我们提出了几个解决方案。 下面是其中的几个:

但是,你应该注意到,你可以能需要为自己工艺一个解决方案。 现在,尽管文章并不是"避免内存泄漏的最佳实践",我希望它指出了一些有趣的问题。

开心编码!

历史记录

  • 2005-11-12: 文章创建。

INT  EXP  Intern  MEMO  内存  资源管理器  
相关文章