【翻译】Jscript内存泄露
当一个系统不能正确管理内存分配时将会导致内存泄露的问题。内存泄露是一个bug。其症状主要表现在系统性能降低和程序失败。
MS的IE浏览器存在大量内存泄露问题,其中最严重的是与JScript的交互。当一个DOM对象包含对一个JavaScript对象(如一个事件处理函数)的引用,同时该JavaScript对象包含对该DOM对象的引用时,则形成了一个循环结构。这本身上并没有问题。当DOM对象或事件处理函数不再包含对其他对象的引用时,垃圾回收器(一个自动的内存管理器)会把他们回收,从而允许对他们的空间重新进行分配。JavaScript的垃圾回收能够理解循环而不会出现混乱。不幸的是,IE的DOM不是被JScript管理的,它有自己的内存管理器,该内存管理器不能够理解循环从而会导致一些混乱。结果是,当循环发生时,内存回收不起作用。内存不被回收意味着内存出现泄露。随着时间的增加会导致内存的不足,当内存没有可用空间时浏览器将会失去响应。
我们可以示范这一点。在第一个程序中,Queuetest1,我们将创建10000个DOM元素(SPAN),同时删除除最新创建的10个元素外的所有元素。当你查看windows任务管理器时,你会发现PF(Page File)使用率基本保持一个稳定值。PF使用率的变化代表内存分配效率差。
接着,我们运行第二个程序,Queuetest2.它和第一个函数功能相同,此外对每个元素增加了鼠标点击事件。在Mozilla和Opear中,PF使用率仍然保持常数。但在IE中我们会观察到PF使用率以大约每秒1M的速率稳定递增。通常这种泄露问题不会出现。但随着AJAX技术的盛行,页面停留时间更长,出现了很多变化,失败变得很常见。
既然IE不能回收循环结构的内存,这个任务就要我们自己来做。如果我们明确的终止循环,IE将能够回收内存。依照MS的解释,闭包(Closures)是导致内存泄露的原因.这当然是严重的错误说法,但这导致MS给了程序员关于如何应对MS的bugs的很坏的建议。结果是在DOM中很容易终止循环,但在JScript中终止循环事实上是不可能的。
当我们使用完毕一个元素时,我们必须清除它的所有事件句柄(event handlers)从而终止循环。我们需要做的是把每个事件句柄的属性设置为null。我们可以直接写在程序中或者可以写一个通用的purge函数来实现。
purge函数把一个DOM元素的引用作为参数。它循环获取元素的属性,如果发现函数,则清除该函数。这样就中断了循环从而允许内存被回收。同时它将查看该元素的所有子元素并同样的清除这些元素中的循环。purge函数在Mozilla和Opera中是无关紧要的,只在IE中是必须的。purge函数必须在删除元素前调用,不论是通过removeChild方法还是设定innerHTML属性。
function purge(d) {
var a = d.attributes, i, l, n;
if (a) {
l = a.length;
for (i = 0; i < l; i += 1) {
n = a[i].name;
if (typeof d[n] === 'function') {
d[n] = null;
}
}
}
a = d.childNodes;
if (a) {
l = a.length;
for (i = 0; i < l; i += 1) {
purge(d.childNodes[i]);
}
}
}
最后我们运行第三个程序,queuetest3.在这个程序中purge函数刚好在删除Dom元素前调用。
附原文:
JScript Memory Leaks
Douglas Crockford
www.crockford.com
When a system does not correctly manage its memory allocations, it is said to leak memory. A memory leak is a bug. Symptoms can include reduced performance and failure.
Microsoft's Internet Explorer contains a number of leaks, the worst of which is an interaction with JScript. When a DOM object contains a reference to a JavaScript object (such an event handling function), and when that JavaScript object contains a reference to that DOM object, then a cyclic structure is formed. This is not in itself a problem. At such time as there are no other references to the DOM object and the event handler, then the garbage collector (an automatic memory resource manager) will reclaim them both, allowing their space to be reallocated. The JavaScript garbage collector understands about cycles and is not confused by them. Unfortunately, IE's DOM is not managed by JScript. It has its own memory manager that does not understand about cycles and so gets very confused. As a result, when cycles occur, memory reclamation does not occur. The memory that is not reclaimed is said to have leaked. Over time, this can result in memory starvation. In a memory space full of used cells, the browser starves to death.
We can demonstrate this. In the first program, queuetest1, we will create 10000 DOM elements (spans), and at the same time, delete all but the 10 most recent. When you run it with the Windows Task Manager's Performance display, you will observe that PF (Page File) Usage remains fairly constant. Changes on PF Usage can be an indicator of memory allocation inefficiency.
Next, run the second program, queuetest2. It does the same thing as queuetest1, except that it adds a click handler to each element. On Mozilla and Opera, the PF Usage is about the same, but on IE we see as steady increase as memory leaks away at a rate of about a megabyte per second. Often this leakage is unnoticable. But as Ajax techniques become more popular, with pages staying in place longer, being subjected to many changes, failure becomes common.
Since IE is unable to do its job and reclaim the cycles, it falls on us to do it. If we explicitly break the cycles, then IE will be able to reclaim the memory. According to Microsoft, closures are the cause of memory leaks. This is of course deeply wrong, but it leads to Microsoft giving very bad advice to programmers on how to cope with Microsoft's bugs. It turns out that it is easy to break the cycles on the DOM side. It is virtually impossible to break them on the JScript side.
When we are done with an element, we must null out all of its event handlers to break the cycles. All we have to do is assign null
to each event handler's property. This can done very specifically, or we can make a generic purge
function.
The purge
function takes a reference to a DOM element as an argument. It loops through the element's attributes. If it finds any functions, it nulls them out. This breaks the cycle, allowing memory to be reclaimed. It will also look at all of the element's descendent elements, and clear out all of their cycles as well. The purge function is harmless on Mozilla and Opera. It is essential on IE. The purge
function should be called before removing any element, either by the removeChild
method, or by setting the innerHTML
property.
function purge(d) {
var a = d.attributes, i, l, n;
if (a) {
l = a.length;
for (i = 0; i < l; i += 1) {
n = a[i].name;
if (typeof d[n] === 'function') {
d[n] = null;
}
}
}
a = d.childNodes;
if (a) {
l = a.length;
for (i = 0; i < l; i += 1) {
purge(d.childNodes[i]);
}
}
}
So finally we run the third program, queuetest3. In it, the purge
function is called just before deleting DOM elements.