DOM 内存泄漏
Joel Webber,GWT 团队
更新时间:2009 年 1 月
你可能会问自己:“为什么我必须使用位域来接收 DOM 事件?”,你可能会问自己:“为什么我不能直接向元素添加事件监听器?”如果你发现自己问了这些问题,可能该深入研究 DOM 事件和内存泄漏的浑浊深渊了。
如果你正在从头开始创建小部件(直接使用 DOM 元素,而不是简单地创建复合小部件),那么事件处理的设置通常如下所示
class MyWidget extends Widget {
public MyWidget() {
setElement(DOM.createDiv());
sinkEvents(Event.ONCLICK);
}
public void onBrowserEvent(Event evt) {
switch (DOM.eventGetType(evt)) {
case Event.ONCLICK:
// Do something insightful.
break;
}
}
}
这可能看起来有点奇怪,但这是有原因的。为了理解这一点,你可能首先需要学习一下浏览器内存泄漏。网络上有一些不错的资源
- http://www.quirksmode.org/blog/archives/2005/02/javascript_memo.html
- http://www.ibm.com/developerworks/library/wa-memleak/
所有这些的最终结果是在某些浏览器中,任何涉及 JavaScript 对象和 DOM 元素(或其他本机对象)的引用循环都有一个令人讨厌的趋势,即永远不会被垃圾回收。之所以如此阴险,是因为这是一种在 JavaScript UI 库中非常常见的创建模式。
想象一下以下(原始 JavaScript)示例
function makeWidget() {
var widget = {};
widget.someVariable = "foo";
widget.elem = document.createElement ('div');
widget.elem.onclick = function() {
alert(widget.someVariable);
};
}
现在,我并不是说你会真正用这种方式构建 JavaScript 库,但这可以说明问题。这里创建的引用循环是
widget -> elem(native) -> closure -> widget
有很多不同的方法会导致相同的问题,但它们往往都会形成一个类似于这样的循环。除非你手动执行(通常是通过清除 onclick
处理程序),否则这个循环永远不会被打破。
开发人员尝试解决这个问题的方法有很多。其中比较常见的一种是在 window.onunload
被触发时遍历 DOM,清除所有事件监听器。这存在两个问题
- 它不会清除不再存在于 DOM 中的元素上的事件。
- 它不处理长期运行的应用程序,而长期运行的应用程序越来越普遍。
GWT 的解决方案
在设计 GWT 时,我们决定内存泄漏是绝对不可接受的。你不会容忍桌面应用程序中出现严重的内存泄漏,浏览器应用程序也应该如此。不过,这也带来了一些有趣的问题。为了避免创建任何内存泄漏,任何可能需要被垃圾回收的小部件都不能与本机元素形成引用循环。没有办法找出“如果小部件没有参与引用循环,它将何时被回收”。因此,在 GWT 术语中,小部件在与 DOM 分离时不能参与循环。
我们如何强制执行这一点?每个小部件都有一个唯一的“根”元素。每当小部件被附加时,我们都会从元素到小部件创建一个唯一的“反向引用”(即 elem.__listener = widget
,在 DOM.setEventListener() 中执行)。这将在小部件被附加时设置,并在小部件被分离时清除。
这让我们回到了 sinkEvents() 方法中使用的奇怪位域。如果你查看 DOM.sinkEvents() 的实现,你会发现它会执行类似于以下的操作
elem.onclick = (bits & 0x00001) ? $wnd.__dispatchEvent : null;
每个元素的事件都指向一个中央调度函数,该函数会查找目标元素的 __listener
扩展名,以便调用 onBrowserEvent()
。这样做的妙处在于,它允许我们设置和清除一个扩展名,从而清除任何潜在的事件泄漏。
这意味着在实践中,只要你不使用 JSNI 设置任何引用循环,你就无法编写一个会在 GWT 中泄漏内存的应用程序。我们会在每次发布时都进行仔细的测试,以确保我们没有在底层代码中执行任何操作来引入新的内存泄漏。
当然,缺点是你不能直接将事件监听器挂钩到是小部件元素子元素的元素。相反,你必须在小部件本身接收事件,并找出它是来自哪个子元素。
但这比在用户机器上泄漏大量内存要好,对吧?