How JavaScript Closures Are Garbage Collected

How JavaScript closures are garbage collected

I tested this in IE9+ and Firefox.

function f() {
var some = [];
while(some.length < 1e6) {
some.push(some.length);
}
function g() { some; } //removing this fixes a massive memory leak
return function() {}; //or removing this
}

var a = [];
var interval = setInterval(function() {
var len = a.push(f());
if(len >= 500) {
clearInterval(interval);
}
}, 10);

Live site here.

I hoped to wind up with an array of 500 function() {}'s, using minimal memory.

Unfortunately, that was not the case. Each empty function holds on to an (forever unreachable, but not GC'ed) array of a million numbers.

Chrome eventually halts and dies, Firefox finishes the whole thing after using nearly 4GB of RAM, and IE grows asymptotically slower until it shows "Out of memory".

Removing either one of the commented lines fixes everything.

It seems that all three of these browsers (Chrome, Firefox, and IE) keep an environment record per context, not per closure. Boris hypothesizes the reason behind this decision is performance, and that seems likely, though I'm not sure how performant it can be called in light of the above experiment.

If a need a closure referencing some (granted I didn't use it here, but imagine I did), if instead of

function g() { some; }

I use

var g = (function(some) { return function() { some; }; )(some);

it will fix the memory problems by moving the closure to a different context than my other function.

This will make my life much more tedious.

P.S. Out of curiousity, I tried this in Java (using its ability to define classes inside of functions). GC works as I had originally hoped for Javascript.

Garbage collection in JavaScript closures

In response to @Beetroot-Beetroot's doubts (which, admittedly, I have, too), I did some more digging. I set up this fiddle, and used the chrome dev-tools' timeline and this article as a guideline. In the fiddle, two almost identical handlers create a closure with 2 date objects. The first only references a, the second references both a and b. Though in both cases only a can ever really be exposed (hard-coded values), the first closure uses significantly less memory. Whether or not this is down to JIC (just in time compilation) or V8's JS optimization wizardry, I can't say for sure. But from what I've read, I'd say it's V8's GC that deallocates b when the tst function returns, which it can't in the second case (bar references b when tst2 returns). I get the feeling that this isn't that outlandish, and I wouldn't at all be surprised to find out that FF and even IE would work similarly.
Just added this, perhaps irrelevant, update for completeness' sake and because I feel as though a link to google's documentation of the dev-tools is an added value of sorts.


It sort of depends, as a simple rule of thumb: as long as you can't reference the classInstance variable anymore, it should be GC'ed, regardless of its own circular references. I've tested quite a lot of constructions, similar to the one you describe here. Perhaps it's worth a look
I've found that closures and mem-leaks aren't that common or easy to get by (at least, not anymore).

But as the accepted answer says: it's nigh impossible to know when what code will leak.
Reading over your question again, I'd say: no, you're not going to leak memory: the classInstance variable wasn't created in the global scope, but it's being passed to various functions (and therefore various scopes). These scopes disintegrate each time the function returns. classInstance won't be GC'ed if it's been passed to another function/scope. But as soon as the last function that references classInstance returns, the object is marked for GC. Sure it might be a circular reference, but it's a reference that cannot be accessed from anywhere but its own scope.
You can't really call that a closure, either: closures happen when there is some form of exposure to the outer scope, which is not happening in your example.

I'm rubbish at explaining stuff like this, but just to recap:

var foo = (function()
{
var a, b, c, d;
return function()
{
return a;
}
})();

The GC will deallocate the mem b,c and d reference: they've gone out of scope, there's no way to access them...

var foo = (function()
{
var a, b, c, d;
return function()
{
a.getB = function()
{
return b;
}
a.getSelf = function()
{
return a;//or return this;
}
return a;
}
})();
//some code
foo = new Date();//

In this case, b won't get GC'ed either, for obvious reasons. foo exposes a and b, where a is an object that contains a circular reference. Though as soon as foo = new Date(), foo loses any reference to a. Sure, a still references itself, but a is no longer exposed: it can reference whatever it bloody well likes. Most browsers won't care and will GC a and b. In fact, I've checked and Chrome, FF, and IE8 all GC the code above perfectly... no worries, then.

When is this scope/closure being garbage collected in javaScript?

Very briefly, garbage collection is a background process of the Javascript interpreter / virtual machine that automatically frees the memory of objects no longer needed by your program.

For example, since you think of it for event listeners: when you remove an event listener (a function usually) from some event dispatcher, it is likely that no other part of the program will have a reference to the event listener. Therefore, the garbage collector can and will (at some unknown time) free the memory that was taken by the event listener.

Since a closure references a scope object, in order to garbage-collect a closure it needs not only be unreferenced, but also the scope.

If I modify a bit your example:

/* some stuff ... */
function add10(a) {
var adder = function (x) {
return function(y) {
return x + y;
}
}

var sum = adder(10); //creates a closure
return sum(a);
}

var thirty = add10(20);

/* Some more stuff ... */

After add10 is called, even though the program continues to execute some more stuff, the garbage collector can free the memory associated to the closure sum, because it is no longer referenced, nor is the scope associated to it.

Whereas in this example:

/* some stuff ... */
function add10AndGet20Adder(a) {
var adders = function (x, y) {
return [function(z) {
return x + z;
}, function(z) {
return y + z;
}]
}

var sums = adders(10, 20); //creates a closure
return [sums[0](a), sums[1]];
}

var result = add10AndGet20Adder(50);
var sixty = result[0];
var add20 = result[1];

/* Some more stuff ... */

When executing some more stuff, sums[0] is no longer referenced, BUT add20 (sums[1]) still has a reference to the scope that references x andy, therefore none of the two closures in sums can be freed by the garbage collector, until add20 is referenced by the program.

I hope this makes it clearer, even though the examples are of course nowhere close to real code. In practice, you need to worry about this only if you develop a long-lived program (such as a single page application or nodeJS server) with complicated usage of closures. Otherwise, the memory usage of the closures is unlikely to be a problem.

This SO question gives more details about Javascript garbage collection: What is JavaScript garbage collection?

JavaScript Closures Concerning Unreferenced Variables

The question is -- what happens to x?

The answer varies depending on theory vs. implementation.

In theory, yes, x is kept alive, because the closure (the anonymous function) has a reference to the binding object of the context of the call to foo, which includes x.

In practice, modern JavaScript engines are quite smart. If they can prove to themselves that x cannot be referenced from the closure, they can leave it out. The degree to which they do that will vary from engine to engine. Example: V8 (the engine in Chrome and elsewhere) will start out with x, y, and even the object that x refers to on the stack, not the heap; then when exiting foo, it looks to see what things still have outstanding references, and moves those to the heap. Then it pops the stack pointer, and the other things don't exist anymore. :-)

So, how can they prove it? Basically, if the code in the closure doesn't refer to it and doesn't use eval or new Function, the JavaScript engine is likely to be able to know that x isn't needed.


If you need to be sure that even if x still exists, the object is available for GC even on older browsers that might be literal (dumb) about it, you can do this:

x = undefined;

That means nothing keeps a reference to the object x used to refer to. So even though x still exists, at least the object it referred to is ready for reaping. And it's harmless. But again, modern engines will optimize things for you, I wouldn't worry about it unless you were faced with a specific performance problem and tracked it down to some code allocating large objects that aren't referenced once the function returns, but don't seem to be getting cleaned up.


Unfortunately, as you pointed out below, there are limits to this, such as the one mentioned in this question. But it's not all doom and gloom, see below under the profile snapshot for what you can do...

Let's look this code in V8, using Chrome's heap snapshot feature:

function UsedFlagClass_NoFunction() {}
function UnusedFlagClass_NoFunction() {}
function build_NoFunction() {
var notused = new UnusedFlagClass_NoFunction();
var used = new UsedFlagClass_NoFunction();
return function() { return used; };
}

function UsedFlagClass_FuncDecl() {}
function UnusedFlagClass_FuncDecl() {}
function build_FuncDecl() {
var notused = new UnusedFlagClass_FuncDecl();
var used = new UsedFlagClass_FuncDecl();
function unreachable() { notused; }
return function() { return used; };
}

function UsedFlagClass_FuncExpr() {}
function UnusedFlagClass_FuncExpr() {}
function build_FuncExpr() {
var notused = new UnusedFlagClass_FuncExpr();
var used = new UsedFlagClass_FuncExpr();
var unreachable = function() { notused; };
return function() { return used; };
}

window.noFunction = build_NoFunction();
window.funcDecl = build_FuncDecl();
window.funcExpr = build_FuncExpr();

And here's the expanded heap snapshot:

no description available

When processing the build_NoFunction function, V8 successfully identifies that the object referenced from notused cannot be reached and gets rid of it, but it doesn't do so in either of the other scenarios, despite the fact that unreachable cannot be reached, and therefore notused cannot be reached through it.

So what can we do to avoid this kind of unnecessary memory consumption?

Well, for anything that can be handled via static analysis, we can throw a JavaScript-to-JavaScript compiler at it, like Google's Closure Compiler. Even in "simple" mode, the beautified result of "compiling" the code above with Closure Compiler looks like this:

function UsedFlagClass_NoFunction() {}
function UnusedFlagClass_NoFunction() {}
function build_NoFunction() {
new UnusedFlagClass_NoFunction;
var a = new UsedFlagClass_NoFunction;
return function () {
return a
}
}

function UsedFlagClass_FuncDecl() {}
function UnusedFlagClass_FuncDecl() {}
function build_FuncDecl() {
new UnusedFlagClass_FuncDecl;
var a = new UsedFlagClass_FuncDecl;
return function () {
return a
}
}

function UsedFlagClass_FuncExpr() {}
function UnusedFlagClass_FuncExpr() {}
function build_FuncExpr() {
new UnusedFlagClass_FuncExpr;
var a = new UsedFlagClass_FuncExpr;
return function () {
return a
}
}
window.noFunction = build_NoFunction();
window.funcDecl = build_FuncDecl();
window.funcExpr = build_FuncExpr();

As you can see, static analysis told CC that unreachable was dead code, and so it removed it entirely.

But of course, you probably used unreachable for something during the course of the function, and just don't need it after the function completes. It's not dead code, but it is code you don't need when the function ends. In that case, you have to resort to:

unused = undefined;

at the end. Since you don't need the function anymore, you might also release it:

unused = unreachable = undefined;

(Yes, you can do that, even when it was created with a function declaration.)

And no, sadly, just doing:

unreachable = undefined;

...doesn't succeed (as of this writing) in making V8 figure out that unused can be cleaned up. :-(

JavaScript garbage collection with closures

(Side note: For anyone wondering whether Ben's code is syntactically correct: Yes, it is, as of ES2015; it wouldn't be in ES5 or earlier. It's using the new "method definition" syntax ES2015 defines for object initializers.)

Re your first question, on this code:

var o = {
foo() {
setTimeout(function() {}, 10000);
}
};

o.foo();
o = null;

The object initially assigned to o is eligible for GC right away, even before the timer callback, because nothing keeps a reference to it after the o = null; statement.

But there are other objects that may or may not be eligible for GC, it depends on theory (the specification) vs. practice (implementation optimizations not ruled out by the spec):

Until the timer callback occurs, the timer mechanism keeps a reference to the anonymous function, and so that anonymous function isn't eligible for GC.

In theory, the anonymous function has a reference to the environment object in which it was created, which in turn has a reference to foo, and so the anonymous function keeps both the environment and foo in memory until the timer fires, releases its reference, and the anonymous function is eligible for GC.

In practice, since the anonymous function doesn't reference foo or any arguments or local variables within foo (and doesn't use eval), a JavaScript implementation is free to optimize and allow foo and that environment object to be GC'd even though the setTimeout call still has a reference to that anonymous function. (And some do indeed do closure optimization, such as V8.) They can do that because we can't see any side-effects from it other than memory consumption or the lack thereof.


Re your additional question, on this code:

var o = {
bar: 0,
foo() {
setTimeout(function() { o.bar; }, 10000);
}
};

o.foo();
o = null;

The object itself is still eligible for GC immediately.

Re foo and the environment object for the call to it:

In theory, they're both retained (as they were above). In practice, I don't know if that affects the optimization that the engine can do around foo and the environment for the call or not. It shouldn't, because the o variable is bound to the global environment, not the one set up by the call to foo, and so the environment chain could be edited to skip the environment created by the call to foo. So engines could optimize it, and at a total guess I'd say they probably do, but I don't know that they do.

how to release closure's memory in javascript?

All objects in javascript is garbage collected, regardless of weather they're involved in a closure or not. In your code above, if you do:

var x = test();

then of course the memory for count cannot be released because it may be used later by x. However if you do:

var x = test();
x = "";

the memory for count will be garbage collected sooner or later.

Also, if you do:

function foo () {
var x = test();
x();
}

foo();

the memory for count would also be garbage collected because x goes out of scope when foo() ends.

The best way to ensure you don't have memory leaks in javascript is to avoid global variables.

Note: The garbage collector in old versions of IE used a simple reference count which means that circular data structures are not garbage collected. This was fixed in either IE6 or IE7 and was never a problem in other javascript engines which normally use a mark-and-sweep garbage collector

javascript: make sure objects in closure are garbage collected

look at this post for some more details on garbage collection.

Since you are sending an anonymous function to .each the function and all within will be garbage collected. Except for one part:

// set the callback, wrapping the vars in its scope
tmp.onload = (function(p,t){
return function(){
// do some stuff

// mark the vars for garbage collection
t.onload = ?
t = ?
p = ?
})(photo, tmp)

The function (function(p,t){ ... })(photo, tmp) will be collected since it is anonymous and no longer referenced. But the function it returns will be added to tmp.onload which will persist.

If you want to make sure things are collected set variables, when you are done with them, to undefined or null. That way the garbage collector will be sure that there is no reference in scope, thus freeing them.

Over how much of its enclosing scope does a (javascript) closure close?

I'm mostly interested into whether there is some kind of specification for these cases

The ECMAScript specification does not really detail this. It simply says that a function closes over the whole lexical environment which includes all variables in all parent scopes, organised in so-called environment records.

Yet it does not specify how an implementation should do garbage-collection - so engines do have to optimise their closures themselves - and they typically do, when they can deduce that some "closed over" variable is never needed (referenced). Specifically, if you do use eval anywhere in the closure, they cannot do that of course, and have to retain everything.

not so much about the behavior of some specific implementation

Regardless, you'll want to have a look at How JavaScript closures are garbage collected, garbage collection with node.js, About closure, LexicalEnvironment and GC and How are closures and scopes represented at run time in JavaScript



Related Topics



Leave a reply



Submit