Closure memory leaks

Brendan Eich brendan at mozilla.com
Sun May 22 15:48:46 PDT 2011


On May 22, 2011, at 3:01 PM, Mike Samuel wrote:

> 2011/5/22 David Bruant <david.bruant at labri.fr>:
>> But the problem could exist anyway if, for instance a function has in its
>> scope an object it doesn't use.
>> Example:
>> ----
>> "use strict";
>> function f(){
>>     var o = {}; // In g's scope, but unused anyway
>>     return function g(){
>>                return 1;
>>            };
>> }
>> ----

SpiderMonkey and I believe other competitive implementations do not entrain the outer environment in such a case.


>> I originally thought that a static analysis of the closure could be enough
>> to determine whether things in the scope will ever be used or not.
>> It turns out it's not the case:

Not ever? Categorical thinking is not helpful when optimizing.


>> ----
>> "use strict";
>> function f(){
>>     var o = {a:1};
>>     return function(e){ // doesn't strictly use o
>>                return eval(e);
>>            };
>> }
>> console.log( f()('o') ); // returns the enclosed o object (at least on FF4)
>> ----
>> So, this leads to a couple of questions:
>> - Is the previous code snippet correct?
>> - In which cases can static analysis be enough?
> 
> When the name "eval" is not mentioned static analysis can conclude
> what needs to be captured by a closure and what doesn't.

Right, and eval is relatively rare.

One can go further and do optimistic type inference, redoing it if eval upsets the party. This requires re-JITting, "on stack replacement", and the like. All doable (and in progress in at least one VM).


>> - Are there other forms of analysis that automatically prevent scope memory
>> leaks?
>> - Are there good practices people could use to avoid such leaks?
>> - Are "let" or "const" of any help here?
> 
> Let and const can help in this case:
> 
> function outer() {
>    function closure(s) {
>      return eval(s);
>    }
> 
>   for (...) {
>     let largeObject = ...;
>   }
> 
>   return closure;
> }
> 
> if largeObject were declared var, then closure would have access to it
> so it would have to be pinned.

Simpler:

  function maker(a, b, c) {
    return function (x) { return (a * x + b) * x + c; };
  }

At least SpiderMonkey optimizes the returned closure into a "flat closure" (after Chez Scheme's "display closures") by copying a, b, and c into the closure evaluated from the return expression. No environment entraining at all.

Chez Scheme went further and did assignment analysis and heap boxing of individual entrained mutable vars. There's also "lambda lifting".

Optimization opportunities abound, it's really not fair to say that JS closures necessarily (eval is not always necessary) mean memory leaks.

/be



More information about the es-discuss mailing list