The Javascript memory leak we all do
atkinson.posterous.com
atkinson.posterous.com
The situation that he describes was a problem in versions of IE prior to ie8 because of limitations on its garbage collector --- specifically that it was unable to collect cycles if the cycle involved a DOM element. This is fixed in ie8 (and, presumably, subsequent versions), and even in ie7 the situation gets cleaned up when the user navigates away from the page (although JS that creates a lot of "throwaway" DOM elements could still get into trouble). So, most programmers really have to worry about this only if still targeting ie6.
(By the way, if you are afflicted with ie6, the jQuery 'data' APIs work around this misbehavior; other libraries often have similar tricks.)
The official scoop from Microsoft: http://msdn.microsoft.com/en-us/library/dd361842%28v=vs.85%2...
function foo(element, a, b) {
element.onclick = function() { /* uses a and b */ };
element = null; // avoid memory leak
}
The closure contains a reference to foo's activation object, not to its properties. As a consequence, you can clear out the element reference after the event handler has been defined and this will break the circular reference.The above technique is also useful when creating anonymous functions for other purposes when element references happen to be lying around. Depending on how long these anonymous functions are kept alive and what you do with the elements in the meantime, this would normally lead to a memory leak (due to the dangling reference to the DOM element, rather than a circular reference as described in the article).
See http://jibbering.com/faq/notes/closures/ for a seriously nerdy deep-dive into closures in JavaScript.
I don't think the OP is confused. Nothing he has said is inaccurate, and you're asserting something he didn't say.
> A proper garbage collector will collect cyclic structures.
Absolutely true, so long as they are garbage. However, nothing the OP says implies that the DOM node is garbage. As I read it, the leak comes from accidentally extending the life of the enclosing scope to the life of the DOM node. That's a real leak (in the web app, not in the browser).
There is a related application-level memory leak if you have some other reference to the closure that doesn't belong to the DOM node, and that applies to all browsers. See http://news.ycombinator.com/item?id=2567847 for a simple way to deal with that.
It's a little sad that you don't expand on this, for those in the audience who do not know of this issue. So I will:
In IE up to IE7, Javascript lives in the jscript engine (equivalent to one V8, Tamarin, Rhino or what have you), but DOM objects are actually COM objects. COM objects are handled with a COM runtime, and there is an interface through which jscript interacts with COM.
MSIE4-7 has no issue with jscript circular references (it uses a mark & sweep GC, immune to the issue), and the COM engine ensures there are no issues with circular references in the DOM (COM uses refcounting but — as in Python for instance — has cycle detections).
Issues, as usual, arise at the boundary: if a jscript object has a reference to a (COM) DOM node, or a DOM node has a reference to a jscript object, it creates a foreign reference which will not be cleaned up until the holder object on the other side of the interface is done with.
For both garbage collectors, those foreign references are completely opaque, the only thing they know is that those exist but they have no way to know what happens on the other side of the jscript-COM interface. This means if there is a cycle crossing the boundary (twice), neither garbage collector is able to clean it up. And there is no Breach, able to take care of boundary issues such as this.
And it leaks.
A lot.
In fact, it leaks so much that a few years ago a tool was tested with the sole purpose of reloading a page and carefully monitoring the objects which fail to be cleaned: drip.
In the other cases, there's no memory leak while the DOM node still exists, because the DOM still has a reference to it so it shouldn't be garbage collected.
> create a circular reference and thus, a memory leak
It was confusing because JavaScript uses mark-and-sweep collection, which can handle circular references just fine.
A bit of googling turns up this link, which explains it:
http://www.ibm.com/developerworks/web/library/wa-memleak/
The problem is that the DOM is reference counted (at least in some browsers, at least back when that article was written). So, you can get memory leaks in cases where JS intersects with the DOM.
That's because it's wrong.
> It was confusing because JavaScript uses mark-and-sweep collection, which can handle circular references just fine.
I don't think that's mandated, you could probably implement a refcounted JS interpreter.
> The problem is that the DOM is reference counted (at least in some browsers, at least back when that article was written). So, you can get memory leaks in cases where JS intersects with the DOM.
And the main culprit was, as is usually the case, Internet Explorer.
To say that we all do this is more than a bit of an exaggeration.
There are plenty of situations where developers can find themselves dealing with insane JavaScript implementations and irresponsible JavaScript frameworks. While the article doesn't explain exactly what the issue is and which browsers it affects (leading me to believe the author himself doesn't truly understand these memory leaks), the recommendation given is a sound one.
Or by most browsers not being broken anymore.
> which browsers it affects
Internet Explorer up to version 7.
And with the way Chrome handles tabs (in separate processes) does it really matter, since closing the tabs releases the memory?
it does not create a leak in any modern browser. The only older browser in which it does is Internet Explorer, up to (and including) version 7.
Can someone explain this a little better?
Since now element has a property which closes over element, he argues this is a circular reference, which will eventually become a memory leak.
It seems to me that a smart JS engine could avoid this by noticing that there's no possibility of using element within the first closure, but I'm not an expert. Also it seems like a small price to pay unless you are constantly creating and destroying such widgets; surely anything associated with an unloaded page can be GC'ed regardless of circular references?
They don't even need that. Any garbage collection strategy (including refcounting) have no trouble reclaiming cycle (the only GC type which may have problems are refcounting GCs, and they usually have a cycle detector for exactly that purpose).
The only way for this issue to arise is to involve two runtimes, each with its own garbage collector, able to create references between one another.
A cycle crossing the boundary will not be detectable by either GC, and will lead to a leak.
This is exactly what happens in Internet Explorer up to (and including) version 7: JavaScript lives in the jscript engine and the DOM lives in COM, each has its own GC, and they can have references to objects living in the other runtime.
function foo(huge) {
some_global_thing.call_later = function() { 42 }
}
And call foo with "huge", huge is not saved in call_later's closure, right? And it doesn't change if I do: function foo(huge, x){
blah_blah_blah.whatever = function() { x + 1 };
}
We're still only closing over "x", right? function foo (x) {
return function(what) {
return eval(what);
};
}
var theClosure = foo(42);
var theX = theClosure("x");
document.write(theX); // displays 42
Even though "x" is never explicitly used, it's still accessible through the eval'd expression.