在Ajax盛行以前,浏览器内存泄漏不是什么大问题,因为都是通过页面跳转和刷新来进行与服务端的交互,而现在情况不一样了,很多应用广泛应用Ajax和iframe,结果内存泄漏成了很多富客户端应用的隐患。比如我现在参与的项目长期以来一直深受内存泄漏问题的困扰,测试人员常常抱怨,因为他们是使用软件最多的人员,常常几个小时后的点击就让浏览器占用的内存达到几百M,有时甚至到G,但我们开发人员始终没有很好解决这个问题,归结其原因,主要是开发人员没有关注内存泄漏的意识,开发时只管功能实现,不管是否造成了内存泄漏,但问题积攒到一定级别时,解决问题的成本就不小了。同时,检测内存泄漏的工具和手段确实有限,仅有的两个工具(JavaScript Memory Leak Detector和 sIEve )都不太好用,不像Java里面的一些工具能精准定位。另外,项目里大量使用了Ext和Jquery框架,这些框架本身就存在内存泄漏的问题,因此,需要常常深入到这些框架的源码,这自然提高了解决问题的难度。
这块骨头难啃,但必须啃下不可,因为造成的影响实在太恶劣,此项目将为服务与千万级别的客户,如果让这千万级别的客户都去忍受动辄占用几百M的应用,实在说不过去。有关内存泄漏的文章很多,比如《Understanding and Solving Internet Explorer Leak Patterns》 是最权威一篇,里面提到了几个造成内存泄漏的模式,摘抄如下:
-
Circular References—When mutual references are counted between Internet Explorer's COM infrastructure and any scripting engine, objects can leak memory. This is the broadest pattern.
-
Closures—Closures are a specific form of circular reference that pose the largest pattern to existing Web application architectures. Closures are easy to spot because they rely on a specific language keyword and can be searched for generically.
-
Cross-Page Leaks—Cross-page leaks are often very small leaks of internal book-keeping objects as you move from site to site. We'll examine the DOM Insertion Order issue, along with a workaround that shows how small changes to your code can prevent the creation of these book-keeping objects.
-
Pseudo-Leaks—These aren't really leaks, but can be extremely annoying if you don't understand where your memory is going. We'll examine the script element rewriting and how it appears to leak quite a bit of memory, when it is really performing as required.
但如果通过这么几个模式去check项目里的代码简直如大海捞针,我只有凭经验预测最有可能泄漏的几个点,再通过排除法,去掉先相关代码,再看内存是否泄漏。我预计会有以下几种情况造成泄漏:
- 频繁操作iframe
- 动态创建DOM
- 事件绑定
- Ext框架本身
经过排查发现项目这么几个点内存泄漏较为严重:
- 对iframe里面的页面大量绑定键盘事件
- 在iframe里面的页面动态创建DOM
- iframe的刷新
- Ext本身
对于这些问题我的解决方案如下:
- 离开iframe里的页面之前删除所有的事件绑定,删除所有动态创建的DOM
- 刷新iframe内容之前调用
iframe.contentWindow.document.write('');
iframe.contentWindow.document.close();
强制释放iframe里的页面元素 - 修改Ext某些点。
比如Ext.getDOC()
getDoc : function(){
//原实现
//return Ext.get(document);
//修改后
var f = function() {
};
f.prototype = Ext.Element.prototype;
var docEl = new f();
docEl.dom = document;
return docEl;
}
具体是什么原因还没查清,不过修改后确实内存释放了不少。 - 修改Ext垃圾回收相关代码。及时删除垃圾回收的轮询。
修改以上点后,内存基本保持在100多M,相比以前改进了不少,但并没完全消除内存泄漏,因此此问题的跟踪还得继续,力争将内存泄漏减为0