Hacker News new | ask | show | jobs
by richardofyork 4733 days ago
First, it is not a bug in JavaScript at all.

Here is a technical explanation of why there is a memory leak and how to fix the problem.

The scope chain of Closures (in JavaScript) contains the outer function(s) activation object. The activation object of a function contains all the variables that the function has access to, and it is part of a function’s scope chain.

This means the inner function (the closure) has access (a reference) to the outer function’s scope chain, including the global object. And even after the outer function has returned, the closure still has access to the outer function’s variables.

Therefore, the activation object of the outer function cannot be destroyed (for garbage collection) after it has returned, because the closure still references its variables.

When the outer function returns, its own scope chain for execution (its execution object) is destroyed, but its activation object is still referenced by the closure, so its variables will not be destroyed until the closure is destroyed.

The execution context of a function is associated with the functions’ activation object, but while the execution object is used for its own execution and is destroyed when it returns, its activation object is referenced by closures—its inner functions. 


Now, as to the specific example in question: The reason the str variable is never destroyed is because it is referenced buy the logIt function because the logIt function's execution object references the entire scope chain of the run function, and the logIt function is never destroyed, so the str variable remains in memory.

As the original author (OP) suggested, be sure to dereference any local variable in the outer function that the closure is using, once the closure is done with it or once the outer function is done with it.

Also, simply setting the logIt function to null (when it completes execution—returns) will allow the str variable and the entire scope of chain of both the logIt and the containing run function to be destroyed and ready for garbage collection.


For a detailed explanation of closures in JavaScript, see "Understand JavaScript Closures With Ease": http://javascriptissexy.com/understand-javascript-closures-w...

1 comments

You seem to be describing behavior that the article did not. Specifically, the retention of 'str' because of the 'logIt' function.

If you read more closely, you'll notice that logIt was not the cause of 'str's retention, but instead, the doSomethingWithStr function was.

When logIt is present by itself, str is not retained. Only when doSomethingWithStr is added alongside logIt is str retained.

nknighthb, I am just wondering. Did you downvote me because you think one part of my explanation was not specific enough?

First, you are correct that I specifically mentioned the logIf function when I discuss the specific example.But that does not take away from my thorough explanation of the main reason for the problem. In fact, everything I said about the logIt function applies to the doSomethingWithStr function, since they are both closures, so my explanation stands as is.

If you read my explanation again, you will see that I clearly explained that closures still have access to the outer function's scope, so both the logIt and the doSomethingWithStr functions have access to the outer function's scope chain even after the outer function or any of the other closures returns.

It is not until both all closures are destroyed or returns that the outer function's scope activation object is destroyed.

If your explanation were all there was to it, then 'str' would not be destroyed when logIt alone were present. But it is.

logIt alone -> str is garbage collected

logIt + doSomethingWithStr -> str is not garbage collected

Your explanation could explain the latter behavior, but it does not explain the former.

Edit: To put it another way, you are addressing only one of the scenarios presented in the article, and you are assuming that scenario results in a behavior the article specifically says does not occur in empirical testing. This is a key point in the article, and your "explanations" are ignoring it.

Are you saying my explanation does not explain the following? "logIt alone -> str is garbage collected"

I don't see why you don't understand why I am saying. My first post explains in detail how closures work and why the issue is not a JS bug, though it is indeed a memory leak. All I can say is that I have explained the overall inner workings of closures in JS, and my explanation covers all the scenarios outlined in the blog post.

This is the crux of the matter of the entire blog post:

"...str is only referenced in the main body of run, and in doSomethingWithStr. doSomethingWithStr itself gets cleaned up once run ends… the only thing from run that escapes is the second closure, logIt. And 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!"

And I explained why that problem is observed and why closures work the way they do.

PS. I will humbly agree to disagree: I think that I have explained all the scenarios outlined in the blog post and you disagree with me. This is not a problem; there is nothing wrong with disagreeing.

Try answering this question specifically: Why is str garbage collected when logIt still exists?
Good question: I take it you are referring to the code below. The reason str is garbage collected is because of the modern implementation of the JS compiler in Chrome. Such modern JS compilers look specifically for such unused variables in closures, even when the variable is in the execution context of the closure.

To clarify, it is Chrome's decision to garbage collect the str variable in that context that resulted in str being garbage collected. It is not a result of a JavaScript bug.

Moreover, different browsers have different implementations for how they garbage collect variables in closures' scope chain. Heck, browsers do their own thing when it comes to many aspects of JavaScript.

On second thought (from our previous back and forth), you are correct that I did not explicitly discuss this specific issue in my earlier post. So you win :) and I have up-voted your two comments accordingly.

Here is the code I think you are referring to: var run = function () { var str = new Array(1000000).join('*'); var logIt = function () { console.log('interval'); }; setInterval(logIt, 100); }; setInterval(run, 1000);