2012年(366)
分类: 系统运维
2012-05-08 15:50:16
泄露的客观因素
在过去,对于web开发人员内存泄露并没有引起大的问题,主要是因为页面都相对比较简单,并且页面也只是在一个站点内跳转(这也是一种很好的释放松散内存的方式);如果有内存泄露的话,那也是小的不足以引起开发人员的注意。
随着技术的发展,新的应用程序需要遵循更高的标准。一个页面或许会运行数个小时而没有跳转页面并通过web服务动态检索数据进行更新。复杂的事件机制、面 向对象开发、闭包等很多语言特性被用来开发整个应用程序。在这些和其他的一些改变下,某些内存泄露模式开始变得更加突出,尤其是那些以前被页面跳转隐藏的 泄露问题。
内存泄露模式
接下来我们将会讨论内存泄露模式,并且每个模式都会给出一些简单的例子。现在,我们简单的看一下以下的模式:
1. 循环引用---当IE中COM对象和js的对象形成循环引用的时候,对象得不到释放就是造成内存泄露。这是一种最广泛的模式。
2. 闭包---作为一种在web应用程序中使用最广泛的模式,闭包是以一种特殊方式的循环引用造成内存泄露的。
3. 跨页泄露---当你从一个页面跳转到另一个页面的时候,由于产生的一些中间对象会形成很微弱的内存泄露。
4. Pseudo-Leaks---这个并不是真正的泄露,但是如果你不理解这个,那么你的内存不断增加也是十分恼人的。
以上是根据自己对文章的理解,对部分文字的翻译,下面我们将以同样的方式,先就翻译,然后记录相应的思考。
循环引用模式
循环引用几乎是所有泄露的根源。正常情况下,js的脚本引擎是可以通过垃圾回收器解
决 循环引用问题的(js的垃圾回收器使用的标记擦除法,前面已经介绍过标记擦除法,这种算法本身是可以解决循环引用的问题),但是某些未知的情况会阻止其正 常工作。在IE中这些未知情况出现在js对象可以访问DOM元素的情况下。其基本的规则原理如下图(循环引用模式原理图)所示
该模式导致内存泄露的原因是基于COM的引用计数器。Js引擎引用一个DOM元素,那么回收和释放DOM元素的引用,只能一直等待到对其引用被解除。在我 们的图中,js引擎存在两个引用:scope对象和DOM元素的expando属性。当js引擎结束的时候将会释放第一个引用,但是对DOM元素的引用并 不会释放,因为它会一直等待js引擎释放对它的引用。你或许会想很容易就可以探测到这种情况并解决这个问题,但是在实践中这只是众多情况中的冰山一角。如 果循环引用是由30个对象的环(类似链表)形成的,那么将会很难探测到。
图 1. 循环引用模式原理图
根据这个原理图,我写了个测试的例子,在函数principlePictureLeak中,我们获取到span元素,并使obj1指向它,然后实例化对象obj4,并使span元素的expando属性指向obj4,具体的代码如下
在内存泄露检测工具sIEve中运行以上代码,我们可以看到并没有内存泄露,具体信息详见 下图(循环引用原理图内存泄露检测图)
图 2. 循环引用原理图内存泄露检测图
那么为什么没有形成内存泄露呢?按照Justin Rogers对循环引用模式原理的解释,上边的图1并没有完全展示出其描述的对象关联关系,那么我们可以看一下这些对象之间的关系(如图3),从图中我们 可以看到对象图中并没有形成真正的”圆环”,自然也就没有形成真的循环引用,在函数执行完以后,js引擎就会释放对方法scope对象的引用,然后 scope对象就会释放对obj1、obj4的引用,然后obj1就会释放对DOM元素的引用;那么这个时候如果执行完函数后,obj4是否会释放内存 呢?
图 3. 循环引用模式原理图的对象关系
那 么我们先来看以下的代码测试,其意图就是在setReference里使span元素引用一个js的对象,然后在getReference里来获取这个对 象并输出其属性msg的值,经测试其最终输出了“DOM引用了js对象”,那么我们可以知道,DOM应用的局部变量并不会因为函数执行完毕而被回收掉。
通 过以上的代码测试,principlePictureLeak执行完成后,并没有回收obj4对象,但是当我们在sIEve中重复刷新页面并没有泄露,那 么也就证明了,在刷新页面的时候,js脚本引擎解除了对DOM的引用,当DOM被回收后,就会释放对obj4的引用,从而成功的回收obj4。
以下是Justin Rogers针对循环引用模式给出的一个例子,代码如下
我们可以简单的看以下各对象之间的引用关系(如图4),从图中我们可以看到myGlobalObject和LeakedDiv之间形成了循环引用的”环路”,这个在理论上构成了内存泄露的条件,那么到底有没有泄露呢,让我们使用sIEve来测试一下吧。
图 4. 循环引用例子1对象关系图
测试结果如下图(图5),让我们跌破眼睛的事情发生了,我们期盼已久的内存泄露并没有出现。那么这又是什么原因呢?那么下边我们看一下我这个可以出现内存 泄露的代码例子。从代码中我们可以看到唯一不同的地方,就是我们新增的DOM元素,而不是直接使用的页面中的DOM元素。
图 5. 循环引用例子泄露测试结果
先来看一下sIEve的测试结果吧,如图6,从图中我们可以看到,随着页面的刷新,内存泄露的数目不断增加,通过对比,我们可以知道循环引用模式产生内存泄露除了满足循环引用外,其中的DOM是动态新增的,而不是页面原有的DOM!
图 6. 我的循环引用例子泄露测试结果
最后总结一下,循环引用模式产生内存泄露除了满足循环引用外,其中的DOM是动态新增的,而不是页面原有的DOM!测试环境window 7 + IE 8。