JavaScript memory leak

JavaScript is secretly a functional programming language, and its functions are closures: function objects get access to variables defined in their enclosing scope, even when that scope is finished.

Local variables which are captured by a closure are garbage collected once the function they are defined in has finished and all functions defined inside their scope are themselves GCed.

1
2
3
4
5
6
7
8
var theThing = null;
var replaceThing = function () {
var originalThing = theThing;
theThing = {
longStr: new Array(1000000).join('*')
};
};
setInterval(replaceThing, 1000);

Every second, we’ll execute the replaceThing function. It replaces theThing with a new object containing a newly allocated giant string, saving the original value of theThing in the local variable originalThing. After it returns, the old value of theThing can be garbage collected, including the long string inside it, since nothing remains that refers to it. So the memory used by this code is roughly constant.

But what if we had a closure that outlasted replaceThing?

1
2
3
4
5
6
7
8
9
10
11
var theThing = null;
var replaceThing = function () {
var originalThing = theThing;
theThing = {
longStr: new Array(1000000).join('*'),
someMethod: function () {
console.log(someMessage);
}
};
};
setInterval(replaceThing, 1000);

Fortunately, modern JavaScript implementations (including, say, the current V8 in Chrome and Node) are smart enough to notice that originalThing isn’t actaully used in the closure someMethod, so it’s not put into someMethod’s lexical environment, and it’s OK to GC the previous theThing when replaceThing finishes.

(But wait, you ask! What if somebody had earlier run console.log = eval, and so the seemingly innocuous line console.log(someMessage) was actually evaling some code that refers to originalThing? Well, the JavaScript standard is one step ahead of you. If you useeval in a sneaky fashion like this (in any way other than by just calling it eval), it’s called an “indirect eval”, and it doesn’t actually get to access the lexical environment! If, on the other hand, someMethod did contain a direct call to eval with that name, it could in fact access originalThing, and the JavaScript environment is forbidden from keeping originalThingout of the lexical environment and this would end up leaking.)

Well, great! JavaScript protects us from memory leaks, right? Well, let’s try one more version, combining the first two examples.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var theThing = null;
var replaceThing = function(){
var originalThing = theThing;
var unused = function(){
if(originalThing)
console.log(“hi”);
};
theThing = {
longStr:new Array1000000).join('*'),
someMethod:function(){
console.log(someMessage);
}
};
};
setInterval(replaceThing,1000);

But isn’t this just the same situation as before? originalThing is only referenced in the main body of replaceThing, and in unused. unused itself (which we never even run!) gets cleaned up once replaceThing ends… the only thing from replaceThing that escapes is the second closure, someMethod. And someMethod doesn’t refer to originalString at all!

So even though there’s no way for any code to ever refer to originalThing again, it never gets garbage collected! Why? Well, the typical way that closures are implemented is that every function object has a link to a dictionary-style object representing its lexical scope. If both functions defined inside replaceThing actually used originalThing, it would be important that they both get the same object, even if originalThing gets assigned to over and over, so both functions share the same lexical environment. Now, Chrome’s V8 JavaScript engine is apparently smart enough to keep variables out of the lexical environment if they aren’t used byany closures: that’s why the first example doesn’t leak.

But as soon as a variable is used by any closure, it ends up in the lexical environment shared byall closures in that scope. And that can lead to memory leaks.

Just add originalThing = null to the end of replaceThing. That way, even though the name originalThing is still in the lexical environment of someMethod, there won’t be a link to the big old value.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var theThing = null;
var replaceThing = function () {
var originalThing = theThing;
// Define a closure that references originalThing but doesn't ever
// actually get called. But because this closure exists,
// originalThing will be in the lexical environment for all
// closures defined in replaceThing, instead of being optimized
// out of it. If you remove this function, there is no leak.
var unused = function () {
if (originalThing)
console.log("hi");
};
theThing = {
longStr: new Array(1000000).join('*'),
// While originalThing is theoretically accessible by this
// function, it obviously doesn't use it. But because
// originalThing is part of the lexical environment, someMethod
// will hold a reference to originalThing, and so even though we
// are replacing theThing with something that has no effective
// way to reference the old value of theThing, the old value
// will never get cleaned up!
someMethod: function () {}
};
// If you add `originalThing = null` here, there is no leak.
};
setInterval(replaceThing, 1000);

So in summary: If you have a large object that is used by some closures, but not by any closures that you need to keep using, just make sure that the local variable no longer points to it once you’re done with it.

https://blog.meteor.com/an-interesting-kind-of-javascript-memory-leak-8b47d2e7f156