简介: 如果大量使用 JavaScript 和 Ajax 技术开发 Web 2.0 应用程序,您很有可能会遇到浏览器的内存泄漏问题。如果您有一个单页应用程序或者一个页面要处理很多 UI 操作,问题可能比较严重。在本文中,学习如何使用 sIEve 工具检测并解决内存泄漏问题,本文也包含内存泄漏问题的应用示例以及解决方案。

发布日期: 2012 年 4 月 09 日 
级别: 中级 
原创语言: 英文 
访问情况 : 10932 次浏览 
评论: 0 (查看 | 添加评论 - 登录)

 平均分 (7个评分)
为本文评分

简介

一般来说,浏览器的内存泄漏对于 web 应用程序来说并不是什么问题。用户在页面之间切换,每个页面切换都会引起浏览器刷新。即使页面上有内存泄漏,在页面切换后泄漏就解除了。由于泄漏的范围比较小,因此常常被忽视。

Ajax 技术引入后,内存泄漏就成了一个比较严重的问题。在 web 2.0 样式页面上,用户不需要经常刷新页面。Ajax 技术用于异步更新页面内容。特殊场景中,整个 web 应用程序构建在一个页面上。在这种情况下泄漏会被累积,不能忽略。

在本文中,了解内存泄漏是怎样发生的,以及如何通过 sIEve 找到泄漏的源头。这些问题和解决方案的的实际示例可以帮助您探究问题。您可以 下载本文示例源代码。

使用 JavaScript 和 Dojo 工具包的经验有助于您理解这篇文章,但并不是必需的。

回页首

泄漏模式

如 web 开发人员所知道的,IE 不同于 Firefox 和其他的浏览器。本文所讨论的内存泄漏模式和问题主要是针对 IE 浏览器的,但不限于 IE。好的方法应该是适用于所有的浏览器的。

IE 怎样管理内存的话题不在本文范围内,参考资料 中有更多信息。

由于 JavaScript 的本质和 JavaScript 和 DOM 对象的浏览器内存管理,JavaScript 编码不慎导致了浏览器的内存泄露。造成了这些泄露的有两种常见的模式。

循环引用
循环引用几乎是每种泄露的根本原因。一般来说,IE 浏览器可以处理循环引用,并将它们正确放置在 JavaScript 环境中。当 DOM 对象被引入时会发生异常。当 JavaScript 对象引用 DOM 元素并且 DOM 元素的属性引用 JavaScript 对象时,循环应用发生并导致 DOM 节点泄露。 清单 1 是一个代码样例,通常用于在文章中演示内存泄漏问题。

清单 1. 循环引用引起的泄露

var obj = document.getElementById("someLeakingDIV");
document.getElementById("someLeakingDiv").expandoProperty = obj;

为了解决这个问题,当您准备把节点移出文档时,一定要将 expandoProperty 设置为空。

闭包
闭包会导致泄露,因为它们会不经意的引起循环引用。当闭包存在的时候,母函数的变量会一直被引用。变量的生命周期超越了函数的作用域,如果处理不当会引起泄露。清单 2 展示了由闭包引起的泄露,这是 JavaScript 的通用编码风格。

清单 2. 泄露的闭包


   <html>
<head>
<script type="text/javascript">
window.onload = function() {
var obj = document.getElementById("element");
// this creates a closure over "element"
// and will leak if not handled properly.
obj.onclick = function(evt) {
alert("leak the element DIV");
};
};
</script>
</head>
<body>
<div id="element">Leaking DIV</div>
</body>
</html>

如果您使用 sIEve — 一个检测孤立节点和内存泄漏的工具 — 您会发现元素 DIV 被引用了两次。其中一个引用是闭包持有的(匿名函数指定给 onclick 事件) 并且即使您删除了节点,也不会被检测到。如果您的应用程序之后删除了 element 节点,JavaScript 引用仍然会持有孤立节点。这个孤立节点将会造成内存泄露。

了解闭包为什么会产生循环引用是非常重要的。文章 “重访 IE 浏览器时的内存泄露” 中的图表清楚地说明了这个问题,并在图 1 中进行了演示。

解决问题的一个方法就是删除闭包。

图 1. 在 DOM 和 JavaScript 之间创建循环引用的闭包

回页首

sIEve 简介

sIEve 是一个帮助检测内存泄露的工具。您可以从 参考资料 中下载 sIEve 和访问文档。主 sIEve 窗口如 图 2 所示。

图 2. sIEve 主窗口

单击 Show in use 时,这个工具非常有用的。您将看到使用的所有 DOM 节点,包括孤立节点和 DOM 节点增加或减少的引用。

图 3是一个样例视图。泄露的原因如下:

  • 孤立节点,在 Orphan 这一列被标记为 “YES” 。
  • 对 DOM 节点增加的不正确引用,显示蓝色。

使用 sIEve 找到泄露节点并查看修复它们的代码。

图 3. sIEve:使用的 DOM 节点

使用 sIEve 找到泄露节点

通过以下步骤检测泄露节点。

  1. 通过您的 web 应用程序的 URL 启动 sIEve。
  2. 单击 Scan Now 寻找当前文档中使用的所有 DOM 节点(可选)。
  3. 单击 Show in use 查看所有 DOM 节点。在这里,所有节点将以红色标识(新条目),因为您刚刚开始。
  4. 使用 web 应用程序的一些功能,测试是否有泄漏。
  5. 单击 Scan Now 刷新使用的 DOM 节点(可选)。
  6. 单击 Show in use。现在,视图中含有一些有趣的信息。可在此找到孤立节点,或者对某个 DOM 节点的异常引用不断增加。
  7. 分析报告并检查您的代码。
  8. 必要时,重复步骤 4-8。

sIEve 不能找出您的应用程序中的所有泄露,但是它能找出由子节点造成的泄露。其他的一些信息,例如 ID 和 outerHTML 可以帮助您指出泄露节点。查看控制泄露节点的代码并相应的作出修改。

回页首

应用示例

这一部分包含更多示可引起内存泄露的示例。这些样例以及最佳实践虽然是基于 Dojo 工具包的,但是大多数示例在普通 JavaScript 编程中是有效的。

虽然有很多方法进行清理,但最常见的方法是删除 DOM 以及 JavaScript 对象以避免内存泄露。本节的其余部分将建立在之前介绍过的模式上。

下面的示例包括一个您可以创建的网站。您还可以从网页中删除网络小部件。这些操作将在一个页面上执行,而页面不会刷新。 清单 3 展示了在 Dojo 类中定义的小部件,这个小部件将在后面文章中频繁出现。

清单 3. MyWidget 类


dojo.declare("leak.sample.MyWidget", null, {
constructor: function(container) {
this.container = container;
this.ID = dojox.uuid.generateRandomUuid();
this.domNode = dojo.create("DIV", {id: this.ID,
innerHTML: "MyWidget "+this.ID}, this.container);
},
destroy: function() {
this.container.removeChild(dojo.byId(this.ID));
}
});

清单 4 展示了操作这些小部件的主页面。

清单 4. 该网站的 HTML 


<html>
<head>
<title>Dojo Memory Leak Sample</title>
<script type="text/javascript" src="js/dojo/dojo/dojo.js"></script>
<script type="text/javascript">
dojo.registerModulePath("leak.sample", "../../leak/sample");
dojo.require("leak.sample.MyWidget"); widgetArray = []; function createWidget() {
var container = dojo.byId("widgetContainer");
var widget = new leak.sample.MyWidget(container);
widgetArray.push(widget);
}
function removeWidget() {
var widget = widgetArray.pop();
widget.destroy();
}
</script>
</head>
<body>
<button onclick="createWidget()">Create Widget</button>
<button onclick="removeWidget()">Remove Widget</button>
<div id="widgetContainer"></div>
</body>
</html>

使用 dojo.destroy() 或 dojo.empty()

乍看之下,这个问题似乎并不重要。小部件被创建并存储在数组中。它们从数组中弹出,并删除。DOM 节点也脱离了文档 。但是如果用 sIEve 追踪 create widget 和 remove widget 操作之间的不同,您会发现每次小部件节点都变成一个孤立节点,它会带来内存泄露。图 4 两次展示了创建和删除小部件的示例。

图 4. 小部件节点的泄露

这种情形可能是一个 IE bug。即使您创建了一个元素并将它附加到文档,然后立即使用 parentNode.removeChild() 删除。孤立节点仍然存在。

您可以使用 dojo.destroy() 或 dojo.empty() 来清理 DOM 节点。Dojo 执行 dojo.destroy(<domNode>) 来删除在其他地方已经删除的节点,然后销毁它们。Dojo 还将创建一个节点收集这种垃圾。这样您想删除的节点就删除了。(查看 Dojo 源代码获取实现细节。)清单 5 展示了修复该问题的方法。

Using 清单 5. 使用 dojo.destroy() 来删除 DOM 节点


## change the destroy() method of MyWidget.js
destroy: function() {
dojo.destroy(dojo.byId(this.ID));
}

使用 sIEve 验证,您会发现第一次删除组件时,Dojo 就创建了一个空 DIV (垃圾)。在随后的添加和删除中,没有 DOM 节点成为孤立节点,因此泄露不会再发生。

使 JavaScript 对 DOM 节点的引用无效

进行清理时,使 JavaScript 对 DOM 节点的引用无效是一个很好的方法。在 清单 3 中,destroy 方法不能使 JavaScript 对 DOM 节点(this.domNode, this.container)的引用无效。多数情况下,这种情形不会导致内存泄露,但当您在更加复杂的应用程序中工作时,其它对象可能引用您的小部件,这时可能会出现问题。

假设您不了解的其他库可是可用的,保持对您小部件的引用,而且由于某些原因,它不能被清除。删除小部件将导致引用的 DOM 节点成为孤立节点。清单 6 显示了更改。

清单 6. 网站的 HTML:添加更多对象 (widgetRepo)来容纳小部件


widgetArray = [];
widgetRepo = {}; function createWidget() {
var container = dojo.byId("widgetContainer");
var widget = new leak.sample.MyWidget(container);
widgetArray.push(widget);
widgetRepo[widget.ID] = widget;
}

现在试着添加或删除组件,然后使用 sIEve 来检测内存泄露。图 5展示了小部件 DIV 的孤立节点,以及不断增加的 widgetContainerDIV 引用。在 Refs 列,widgetContainer DIV 应该在文档中只有一个引用。

图 5. 孤立节点

解决方案就是在清理过程中使 DOM 节点引用无效,如 清单 7 所示。可能时添加一些无效语句可能是一个好方法,因为这不会影响原始功能。

清单 7. 使 DOM 引用无效


## the destroy method of MyWidget class
destroy: function() {
dojo.destroy(dojo.byId(this.ID));
this.domNode = null;
this.container = null;
}

断开事件以及取消主题订阅

使用 Dojo,另一个避免内存泄露的方法就是断开您连接的事件并取消您订阅的主题。 清单 8 展示了一个连接及断开事件的例子

使用 JavaScript 编程,通常建议在从文档中删除 DOM 节点之前先断开事件。使用下述的 API 在不同的浏览器上连接及断开事件。

  • 对于 IE:attachEvent 和 detachEvent
  • 对于其他浏览器:addEventListener 和 removeEventListener

清单 8. Dojo.connect and dojo.disconnect 


## the constructor method of MyWidget class
constructor: function(container) {
// … old code here
this.clickHandler = dojo.connect(
this.domNode, "click", this, "onNodeClick");
} ## the destroy method of MyWidget class
destroy: function() {
// … old code here
dojo.disconnect(this.clickHandler);
}

在 Dojo 中,您还可以通过订阅和发布主题在组件中建立连接。它作为 Observer 模式执行。在这种情况下,避免内存泄漏的最好方法是做清理时取消主题订阅。对着这两种方法使用下列 API:

  • dojo.subscribe(/*string*/topic, /*function*/function)
  • dojo.unsubscribe(/*string*/topic)

设置 innerHTML

如果您在如何使用 JavaScript 设置 innerHTML 方面不细心的话,可能会引起 IE 内存泄露。(查看 参考资料 获取详情。) 清单 9 展示了可能引起 IE 内存泄露的场景。

清单 9. IE 上的 innerHTML 泄露

// 1. An orphan node should be in the document
var elem = document.createElement(“DIV”); // 2. Set the node’s innerHTML with an DOM 0 event wired
elem.innerHTML = “<a onclick=’alert(1)’>leak</a>”; // 3. Attach the orphan node to the document
document.body.appendChild(elem);

以上显示的代码类型在 Web 2.0 应用程序中是很常见的,因此要小心对待。解决方案就是确保这个节点在设置 innerHTML 之前不是一个孤立节点。清单 10 是对清单 9 中代码的修复。

清单 10. 修复 innerHTML 泄露


var elem = document.createElement(“DIV”);

// 现在节点不再是叶子节点
document.body.appendChild(elem); elem.innerHTML = “<a onclick=’alert(1)’>no leak</a>”;

回页首

结束语

识别导致浏览器内存泄露的模式很容易,而在您应用程序源代码寻找问题的根源就比较困难。sIEve 能够帮助您找到大多数由孤立节点引起的泄露。本文介绍了,在 JavaScript 编码中仅仅一点微小的疏忽就会引起内存泄漏。本文中介绍的最佳实践可以帮助您防止发生泄漏 。

回页首

下载

描述 名字 大小 下载方法
本文源代码 MyWidget.zip 1KB HTTP

关于下载方法的信息

参考资料

学习

获得产品和技术

讨论

关于作者

Yi Ming Huang 是在 China Development Lab 从事 Lotus ActiveInsight 的软件工程师。他擅长与 Portlet/Widget 相关的 Web 开发并对 REST、OSGi 和 Spring 技术感兴趣。

from:http://www.ibm.com/developerworks/cn/web/wa-sieve/

找出并解决 JavaScript 和 Dojo 引起的浏览器内存泄露问题的更多相关文章

  1. 在 Linux 上找出并解决程序错误的主要方法【转】

    转自:https://www.ibm.com/developerworks/cn/linux/sdk/l-debug/index.html 本文讨论了四种调试 Linux 程序的情况.在第 1 种情况 ...

  2. JavaScript之详述闭包导致的内存泄露

    一.内存泄露 1. 定义:一块被分配的内存既不能使用,也不能回收.从而影响性能,甚至导致程序崩溃. 2. 起因:JavaScript的垃圾自动回收机制会按一定的策略找出那些不再继续使用的变量,释放其占 ...

  3. JavaScript 中 4 种常见的内存泄露陷阱

    了解 JavaScript 的内存泄露和解决方式! 在这篇文章中我们将要探索客户端 JavaScript 代码中常见的一些内存泄漏的情况,并且学习如何使用 Chrome 的开发工具来发现他们.读一读吧 ...

  4. JavaScript垃圾回收(三)——内存泄露

    一.JavaScript内存监测工具 在讨论内存泄露之前,先介绍几款JavaScript内存监测工具. IE的sIEve与JSLeaksDetector(这两个可以在下面的附件中下载),firefox ...

  5. JavaScript:4个常见的内存泄露

    什么是内存泄漏 内存泄漏基本上就是不再被应用需要的内存,由于某种原因,没有被归还给操作系统或者进入可用内存池. 编程语言喜欢不同的管理内存方式.然而,一段确定的内存是否被使用是一个不可判断的问题.换句 ...

  6. 160715、在web.xml中注册IntrospectorCleanupListener解决Quartz等框架可能产生的内存泄露问题

    增加方式如下:web.xml中加入  <listener>    <listener-class>org.springframework.web.util.Introspect ...

  7. Javascript内存泄露

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

  8. [ Javascript ] 内存泄露以及循环引用解析

    内存泄露 在javascript中,我们非常少去关注内存的管理. 我们创建变量,使用变量,浏览器关注这些底层的细节都显得非常正常. 可是当应用程序变得越来越复杂而且ajax化之后,或者用户在一个页面停 ...

  9. c#封装DBHelper类 c# 图片加水印 (摘)C#生成随机数的三种方法 使用LINQ、Lambda 表达式 、委托快速比较两个集合,找出需要新增、修改、删除的对象 c# 制作正方形图片 JavaScript 事件循环及异步原理(完全指北)

    c#封装DBHelper类   public enum EffentNextType { /// <summary> /// 对其他语句无任何影响 /// </summary> ...

随机推荐

  1. mfc主窗口添加背景图片后,如何实现在背景图片上输出文字

    1.若是文档视图程序的话,在视图类的OnDraw(CDC* pDC)函数中调用pDC->TextOut()函数,就像平常输出文字一样.若是嫌文字的背景颜色破坏了图像,可以在输出文字之前调用pDC ...

  2. Iframe知识点

    var oIframe=document.getElementById('iframe1'); 获取iframe对象:   oIframe.contentWindow.//  iframe 里的win ...

  3. Android 网络交互之MD5为什么要加盐

    MD5为什么要加盐 之前面试的时候,遇到一个面试的哥哥.不停的跟我确认我对网络传输过程中的password进行MD5加密的时候,是否加key了. 当时我很纳闷,因为MD5本身已经是不可逆的了,需要破解 ...

  4. 安装andriod studio时出现Internal error. Please report to https://code.google.com/p/an

    有两种方法的哈 一,在文件中添加  disable.android.first.run=true (我试了这种就好使了哈) 1)进入刚安装的Android Studio目录下的bin目录.找到idea ...

  5. Linux 终端部分重要快捷键

    tab  自动补全文件名,目录名或命令名ctrl+c  强行终止当前程序Ctrl+d 键盘输入结束或退出终端 Ctrl+s 暂定当前程序,暂停后按下任意键恢复运行 Ctrl+z 将当前程序放到后台运行 ...

  6. wiki oi 1044 拦截导弹

    题目描述 Description 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度.某 ...

  7. Android 开机动画源码分析

    Android系统在启动SystemServer进程时,通过两个阶段来启动系统所有服务,在第一阶段启动本地服务,如SurfaceFlinger,SensorService等,在第二阶段则启动一系列的J ...

  8. CSS的三种样式:内联式,嵌入式,外部式以及他们的优先级

    从CSS 样式代码插入的形式来看基本能够分为下面3种:内联式.嵌入式和外部式三种. 1:内联式css样式表就是把css代码直接写在现有的HTML标签中,如以下代码: <p style=" ...

  9. ceph源码之一

    转自于:http://blog.csdn.net/changtao381/article/details/8698935 一.概述: 其结构如下:在src 里, 网络通信:  msg  里面 包括了网 ...

  10. iOS 拍照保存到相册

    之前看了一些开源的代码,里面有一个功能,就是将图片下载到相册,仔细看了其中的代码,只有很简单的一句话,并且保存过后,还可以判断是否保存成功. 如下代码所示, 点击按钮,将self.imageView上 ...