Closure memory leak of unused variables

Closure memory leak of unused variables



I'd like to understand under which circumstances variables which are no further used are stored in closures and lead to memory leaks. My most preferred outcome would be "there are none", but this doesn't seem to be the case.



From what I understand, once a function is declared inside another function, its internal [[scope]] is assigned the LexicalEnvironment of its encapsulating function. This LexicalEnvironment has reference local variables and the entire scope chain at that point. This basically includes all free variables the function could access (from what I understood of lostechies, javascript closures explained).



Here the first issue arises: this should mean all those variables can be reached as long as the function lives. E.g. the following should already leak:




function a()
let big = new Array(1000000).join('*'); //never accessed
//function unused() big;
return () => void 0;


let fstore = ;
function doesThisLeak()
for(let i = 0; i < 100; i++) fstore.push(a());


doesThisLeak();



This luckily doesn't seem to be the case on my firefox. I've received several explanations to why this doesn't leak, from "the jitter is smart" to "LexicalEnvironment is a record type which means GC can collect the unused variables". I still don't know whether either is correct, whether this doesn't leak on all modern runtimes and why.



After further research, I found auth0, four types of leaks in javascript (sadly, there appears to be no html id to jump to, the relevant part is "4: Closures") which shows a way to trick whatever smart thing is collecting the unused variables. In above snippet, when just uncommenting the "unused" function, I do not see RAM usage ever going down again (it was already noted that it could be GC simply did not run for other reasons. However, so far, I am assuming it leaks. I also got told this was limited to firefox, but it appeared to produce similar behavior in chrome)



This example (in case it really does what i believe it does), shows that completely unused variables can leak due to a function declaration in the same scope.



To conclude my problems:



PS: It is quite hard to make certain that this question has not already been asked in the jungle of questions about memory leaks with closures.






Modern engines will keep only those variables from outer scopes actually used within the inner functions. In your case, since the returned inner function does not use big, no reference to it will be maintained. if instead your inner function was return () => big;, then yes, big would be maintained, but it would have to be, right? Whatever memory was involved would be GC'd anyway once fstore went out of scope. You can't garbage collect something which isn't garbage, and not doing so is not a "leak".

– user663031
Aug 8 '16 at 20:52



big


return () => big;


big


fstore






sorry what's GC again?

– Martian2049
Sep 6 '17 at 2:20




3 Answers
3



The compiler can examine the code of the returned function to see which free variables it references, and only those variables need to be saved in the closure, not the entire LexicalEnvironment. You can see this by examining the closure in the Javascript debugger.




function a()
let big = new Array(1000000).join('*');
let small = "abc"; // is accessed
return (x) => small + x;


fun = a();
console.dir(fun);

function b()
let big = "pretend this is a really long string";
function unused() big;
return () => void 0;


fun = b();
console.dir(fun);



When you expand the first function in the debugger, you'll see small in the Closure property, but not big. Unfortunately, the Chrome compiler doesn't seem to be clever enough to detect when the variable is referenced in an unused function that isn't returned, so it doesn't need to be saved, so we get a leak in b().


small


Closure


big


b()



enter image description here



Any data that isn't saved in the closure becomes garbage and can be collected, so it won't leak.






Thanks, this solves a large portion of the problem. Issues that remain: "The compiler can examine", is this not by spec, can I depend on this? Also are there more pitfalls like the described one, where a reference in an unused function tricks the compiler into thinking it is needed?

– ASDFGerte
Aug 8 '16 at 21:56







It's a compiler optimization, so I don't think you can depend on it. As you've seen, it's not perfect.

– Barmar
Aug 8 '16 at 21:59






As for best practices on code, any suggestions? I guess hoping for this compiler optimization to occur in all modern browsers and explicitly nulling memory-intensive non-exposed local variables when it gets more complicated is a start. Are there more known situations where this optimization fails? I'd prefer not to always have to null all variables at the end of functions.

– ASDFGerte
Aug 8 '16 at 22:20






Unless the closure is very long-lived, like an event handler, I wouldn't worry too much about it. If it's just a one-time callback, it will presumably be called pretty soon, and then discarded, so the entire closure will become garbage. And you don't have to null all variables, just the ones that are likely to consume huge amounts of memory, the rest are probably not going to cause much impact.

– Barmar
Aug 9 '16 at 1:20






And don't forget: premature optimization is the root of all evil. Wait until you actually have a memory problem before worrying about this.

– Barmar
Aug 9 '16 at 1:28



After you call a() your fstore has reference to created function () => void only. After a() returns, its scope vars will be deleted. It means that no vars has reference to your new Array(1000000).join('*') and it will be garbage collected. It will be collected the same way if you uncomment unused function because it will deleted too. There are no leaks in your code.


a()


fstore


() => void


a()


new Array(1000000).join('*')


unused






Using chrome's debugger like Barmar said, you can see that when "unused" is uncommented, "big" is being closed over (at least in my chrome). SS of debugger

– ASDFGerte
Aug 8 '16 at 21:46







@ASDFGerte my fail, Barmar explained this. But i think its browsers fail, it's should not be stored in closure. If you breakpoint your () => void 0 function execution you cant access big in console, it says undefined for me.

– Maxx
Aug 8 '16 at 22:54



() => void 0


big


undefined



pic1



you can think about Javascript scope chain with the problem, Identify a Function will create a new scope chain , if function's local scope not have a local variable, it will reference outside lexical environment scope, it will keep over in the memory



here's a link



Thanks for contributing an answer to Stack Overflow!



But avoid



To learn more, see our tips on writing great answers.



Required, but never shown



Required, but never shown




By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

Edmonton

Crossroads (UK TV series)