This week, my teammates at Meteor tracked down a surprising
this is a relatively unknown issue. (Most of what you find for that query talks
about the bad garbage collection algorithm in old versions of IE, but this
problem affects even my current Chrome install!) UPDATE: Vyacheslav Egorov
pointed me at his excellent post on this subject with more detailed
examples, better pictures, and knowledge of V8 internals. Definitely adding his
blog to my
Also, there’s a great paper about this issue with an optimal implementation,
Now, consider this code:
Every second, we’ll execute the
run function. It will allocate a giant string,
create a closure that uses it, invoke the closure, and return. After it returns,
the closure can be garbage collected, and so can
str, since nothing remains
that refers to it.
But what if we had a closure that outlasted
run allocates a giant string, and starts an interval that logs
something 10 times a second.
logIt does last forever, and
str is in its
implementations (or at least current Chrome) are smart enough to notice that
str isn’t used in
logIt, so it’s not put into
logIt’s lexical environment,
and it’s OK to GC the big string once
Open up the Timeline tab in your Chrome Developer Tools, switch to the Memory view, and hit record:
Looks like we’re using an extra megabyte every second! And even clicking the
garbage can icon to force a manual GC doesn’t help. So it looks like we are
But isn’t this just the same situation as before?
str is only referenced in
the main body of
run, and in
gets cleaned up once
run ends… the only thing from
run that escapes is the
logIt doesn’t refer to
str at all!
So even though there’s no way for any code to ever refer to
str 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
str, it would be important that they both get the same object,
str gets assigned to over and over, so both functions share the same
enough to keep variables out of the lexical environment if they aren’t used by
any 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 by all closures in that scope. And that can lead to memory leaks.
You could imagine a more clever implementation of lexical environments that avoids this problem. Each closure could have a dictionary containing only the variables which it actually reads and writes; the values in that dictionary would themselves be mutable cells that could be shared among the lexical environments of multiple closures. Based on my casual reading of the ECMAScript 5th Edition standard, this would be legitimate: its description of Lexical Environment describes them as being “purely specification mechanisms [which] need not correspond to any specific artefact of an ECMAScript implementation”. That said, this standard doesn’t actually contain the word “garbage” and only says the word “memory” once.
Fixing memory leaks of this form, once you notice them, is straightforward, as
demonstrated by the fix to the Meteor bug. In the example above, it’s
obvious that we’re intending to leak
logIt itself, just not
str. In the
original Meteor bug, we didn’t intend to leak anything: we just wanted to
replace an object with a new object and allow the previous version to be freed,