2011年5月20日星期五

Memory Leaks in IE8 (and IE9)

In June 2007, Microsoft famously fixed a problem with memory leaks in Internet Explorer 6. IE8 leaks memory worse than IE6 ever did, yet I haven’t been able to find any mention of it. I say worse because:

  1. No closures or circular references are required.
  2. I cannot find a workaround.
Problem statement

The memory used by IE8 to create certain DOM nodes is never freed, even if those DOM nodes are removed from the document (until window.top is unloaded). There are no closures or circular references involved.

The DOM node types that leak are form, button, input, select, textarea, a, img, and object. Most node types don’t leak, such as span, div, p, table, etc.

This problem only occurs in IE8. It does not occur in IE6, IE7, Firefox, or Chrome.

In case you missed it: every image element, anchor element, etc. added to a page uses memory that cannot be reclaimed even if the element is removed from the page.

In practical terms

Think intranet web application rather than Internet web site—that is, something that you may leave running for multiple days. If you have a web page that never reloads as a whole but pulls over many updates from the server (e.g., images and anchors are updated Ajax-style), each update will leak memory. Note that update here means existing DOM nodes are removed and new DOM nodes are created.

Depending on the size and frequency of the updates, IE8 can end up using all available virtual memory, start thrashing within itself, and eventually failures occur; see below for more details.

The user hitting F5 occasionally (if the page can handle it) will unload window.top and free its memory, but there is no apparent programmatic workaround. If the page is in an iframe, unloading the iframe has no effect. It seems that window.top must be unloaded.

In terms of code
First example

Here’s the basic idea of the leak, which is to repeatedly execute the following:

function leak1() {
var node = document.getElementById("TO_AREA");
node.innerHTML = "<img>";
node.innerHTML = "";
node = null;
}

Notes:


  • You can leave off the second innerHTML assignment, as well as setting the node to null, which I put in just for clarity/emphasis.
  • There’s an initially empty div with an id of TO_AREA in the body.
  • Example innerHTML that doesn’t leak: <span></span>. See the problem statement above for a list of problematic node types.
  • As I mentioned, there are no closures or circular references involved here.

Second example

Here’s a similar piece of code that avoids innerHTML and has the same leak:

function leak2() {
var node = document.getElementById("FROM_AREA").cloneNode(true);
node.id = "NEW_AREA";
document.body.appendChild(node);
document.body.removeChild(node);
node = null;
}

Notes:


  • The FROM_AREA contains HTML like in the first example. Still, there are no closures or circular references.

Third example

This is the simplest and most straightforward:

function leak4() {
var node = document.createElement("IMG");
document.body.appendChild(node);
document.body.removeChild(node);
}

Replace IMG with (e.g.) SPAN, and there is no leak.

Other notes

These examples leak in IE8 but not other browsers. I’ve tried IE8 on Windows XP, Windows Vista, Windows Server 2008, Windows Server 2008 R2, and Windows 7. I’ve tried IE8 in IE7 backwards compatibility mode, as well as every possible variation of quirks and standards mode. Though that’s by no means every variation of everything, it makes me comfortable that it’s not a fluke.

Live example

Here’s a link to an example page that shows the problem highly amplified. What I mean byamplified is that the job of the page is to show the leak, highly exacerbated; it has no other purpose. There are start/stop links on the page, so don’t worry about the page doing something terrible on its own.

http://test.hemiola.com/leak-ie8.html

Characterizing the leak

The memory size (called private working set on Windows 7) of iexplore.exe grows steadily over time, but this doesn’t lead directly to failures. IE memory usage does seem capped, and it does seem to be trying to manage memory from a DHTML perspective within certain constraints.

After it reaches its self-imposed cap, it seems to start thrashing within itself. Operations that could be performed at maybe 100 times per second begin taking over one minute per operation. Eventually, DHTML-style out-of-memory errors can occur.

In closing

I’m very interested in hearing about workarounds to this problem. I will post any meaningful updates as they are uncovered.

没有评论:

发表评论