Skip to main content

IE, Memory Management, and You

Posted by driscoll on November 13, 2009 at 12:59 PM PST

In a recent blog, commenters took me to task for a perceived IE 6 memory leak. It wasn't actually there (they were wrong), but in attempting to prove myself right, I found a couple of memory leaks under IE in JSF's Ajax support. Since I just spent a week learning how all this functioned, I thought I'd set it down so that others could learn from my efforts.

Now, none of the information that I'll present here is new - it's been discussed among Ajax programmers for at least the last 4 years. If you're a web guru, it's likely that you're not going to learn anything new here (thought I'd welcome any additional information and corrections). But at least a couple of the points I'll illustrate below are either poorly communicated or misunderstood. I'll include a number of links at the end of this article. There are also very significant differences between IE 8 (which mostly works), IE 7 (which is bad), and IE 6 (which is just awful). I'll try to point out the differences as they matter for each.

Tools

First - use the right tool for the job: In order to spot leaks, you'll need to download a tool that can detect them. By all accounts, sIEve is the way to go. It uses IE itself, and introspects to get it's data. The UI is pretty primitive, but I can't recommend it enough - it's truely invaluable. Since it uses IE for it's work, you'll need to run it on a machine that has IE6 installed - presumably in a VM. You'll also want to have it running on a machine that has IE 7 and IE 8 as well, just to be sure. XP fits nicely on a VM that runs on my Mac, and this is how I use it.

Cyclic Leak

Now that that's out of the way, it's time to talk about the very worst of the memory leaks in IE - the dreaded cyclic reference, which the commenters thought that I'd committed. Under certain conditions, IE 6 will "leak" DOM nodes, retaining them, and the javascript objects that point to them, until the browser is either shut down, or crashes entirely due to lack of memory. Ugh! To understand how this happens, you really only need to know two things:

  1. IE 6 (and 7!) reportedly has very primitive garbage collection using reference counting
  2. There are two memory spaces in IE, one for JavaScript, and the other for the DOM, and they don't communicate well.

What could go wrong? Well, lots. The commenters thought that the rule was: A leak will occur if any reference is made in JavaScript to an element that isn't eventually set to null. That's close, but not quite correct. The real rule is: A leak will occur if the JavaScript code contains any reference to the DOM that isn't released in some way, either by going out of scope or being explicitly unset.

When IE 6 sees a JavaScript variable that is pointing to something in the DOM (typically, an element or node), it will record that reference, and not collect it - even when you surf over to a new page. And the DOM won't be collected, since there's a reference to it from JavaScript. These two objects, and all the stuff that references them, will stick around until shutdown. In IE 7, the geniuses at Microsoft saw the bug, and said "Hey, I know how to fix that, let's garbage collect everything when we leave the page.". Nice improvement, but it still doesn't fix the bug, since if you're developing a page that is designed to be used for a long period of time (like many page-as-application apps are now), it'll still crash the browser. Apparently, they saw the error of their ways eventually, since this behavior is no longer present in IE8. (All this is confirmed by my testing with sIEve.)

So, in the example that had in my previous blog, there was no memory leak, because the variable that pointed to the element eventually went out of scope. So - how to you create variables that don't go out of scope? The easiest way is to put them in an object - this was the leak that I eventually found in JSF. The fix there was to null out the object manually. But there's another, more insidious way to create an object - create a closure. That creates a function object implicitly under the window object, and that will never go out of scope. But the key thing to remember is that you need to be aware of when things go out of scope when coding in IE, and act accordingly.

But wait! There's more

If that was the only problem, life would have been fairly easy for me the last week. But that's not the only bug that the Web Wizards of Redmond chose to deliver to their unsuspecting consumers. There's another bug in IE (again, only in IE 6 and 7 - IE 8 appears to have fixed it per my testing), which also leaks DOM nodes that aren't cleaned up until you leave the page. Apparently, when the IE DOM receives a call from the removeChild or replaceChild functions, it doesn't actually, err, remove the nodes. It just leaves them there, hanging around the DOM like party guests that don't have the sense to leave after the host has started handing out coats. While these nodes will eventually be cleaned up when the user leaves the page, this still causes problems for page-as-app programs, as in the cyclic leak for IE 7, above. While the removeChild call is fairly notorious for this, I had to find out about replaceChild with my own testing (though I did find a few obscure references once I went looking for it).

That means that instead of saying node.parentNode.replaceChild(newNode, node), you instead should say something like: node.parentNode.insertBefore(newNode, node); deleteNode(node); (with an appropriate if statement for isIE(), and a deleteNode function that doesn't use removeChild). And instead of saying node.parentNode.removeChild(node); you instead are reduced to coding something like: node.outerHTML = ''; (again, with browser check). Except that when you combine that with IE's horrible problems with manipulating tables, it may fail. So instead, you're probably better off with something like this:

 
                var temp = document.createElement('div');
                try {
                    temp.appendChild(node.parentNode.removeChild(node));
                    temp.innerHTML = ""; // Prevent leak in IE
                } catch (e) {
                    // at least we tried
                }
                deleteNode(temp);

Again, possibly with an isIE() check.

Hopefully you found this description of IE's Memory "Management" useful. Here's a few of the links that I used for research, that I found the most helpful.

As always, I look forward to any comments. Especially about this topic - I'm far from expert in this area.

UPDATE: John Resig just posted about a very interesting looking tool. Haven't checked it out yet, but if it's got him excited...

Related Topics >>

Comments

There is also one more awful

There is also one more awful behaviour of MS IE, at least 6 and 7 related to DOM updates. These browsers start downloading images ( and other referenced objects ) for HTML elements that have been created by JavaScript, even though for objects that are not attached to page. When these elements are inserted on actual place, browser stops loading by closing TCP connection that causes IOException on server and starts download of the same objects again. Therefore, you can see a lot of error records in the server log and unexpected network connections that slow down client application. To avoid such problem it had better to create updated content in place using inner/outerHTML properties and avoid temporary objects for DOM fragments.