I am trying to understand why the following code causes a memory leak
var aThing = null;
var outer = function() {
console.log('running');
var something = aThing;
var closure1 = function() {
if (something) {
console.log('something');
}
};
aThing = {
str: new Array(1000000).join('8'),
someMethod: function() {}
};
};
setInterval(outer, 1000);
Here is the timeline showing memory increasing from Google Chrome:
but this code which is a very slight variation does not cause the same memory leak:
var aThing = null;
var outer = function() {
console.log('running');
var something = aThing;
var closure1 = function() {
if (something) {
console.log('something');
}
}
aThing = {
str: new Array(1000000).join('8')
};
function someMethod() {};
};
setInterval(outer, 1000);
Here is the equivalent timeline showing that GC is cleaning up OK.
I understand that in the first version there is a memory leak because the variable 'something' does not get cleaned up. Why is it being GC'ed in the second example but not the first?
The primary answer is that in your second code block, neither of the closures (
closure1
orsomeMethod
) survives the return ofouter
(nothing outsideouter
refers to them), and so there's nothing left that refers to the context where they were created, and that context can be cleaned up. In your second code block, though,someMethod
survives the return, as part of the object that you're assigning toaThing
, and so the context cannot be GC'd.Let's follow what happens with your first block:
After the first execution of
outer
, we have (ignoring a bunch of details):after the second execution:
after the third execution:
You can see where this is going.
Since the second block never retains a reference to
closure1
orsomeMethod
, neither of them keeps the context in memory.I'm slightly surprised that V8 (Chrome's JavaScript engine) doesn't optimize this leak away, since only
someMethod
is retained, andsomeMethod
doesn't actually usesomething
orclosure1
(oreval
ornew Function
ordebugger
), so although in theory it has references to them via the context, static analysis would show that they can't actually be used and so could be dropped. But closure optimization is really easy to disturb, I guess something in there is disturbing it.