admin管理员组

文章数量:1302329

I was reviewing the slides in this presentation:

and on one of the slides, this code is presented with the suggestion that it creates a memory leak:

var a = function () { 
   var smallStr = 'x',
   largeStr = new Array(1000000).join('x'); 

   return function (n) { 
        eval(''); //maintains reference to largeStr
        return smallStr; 
   }; 
}();

Closures can be another source of memory leaks. Understand what references are retained in the closure.

And remember: eval is evil

Can someone explain the issue here?

I was reviewing the slides in this presentation: http://slid.es/gruizdevilla/memory

and on one of the slides, this code is presented with the suggestion that it creates a memory leak:

var a = function () { 
   var smallStr = 'x',
   largeStr = new Array(1000000).join('x'); 

   return function (n) { 
        eval(''); //maintains reference to largeStr
        return smallStr; 
   }; 
}();

Closures can be another source of memory leaks. Understand what references are retained in the closure.

And remember: eval is evil

Can someone explain the issue here?

Share edited Aug 6, 2013 at 22:26 Mike Samuel 121k30 gold badges227 silver badges252 bronze badges asked Aug 6, 2013 at 22:09 MedicineManMedicineMan 15.3k33 gold badges104 silver badges154 bronze badges 5
  • 3 I don't believe that is a memory leak as defined by the same presentation (which said a leak is "when a program repeatedly fails to return memory that it has obtained for temporary use"), because it's not happening repeatedly. But largeStr will be tying up a chunk of memory until a goes out of scope. Also eval() isn't evil, it's just almost always the wrong tool for the job. – nnnnnn Commented Aug 6, 2013 at 22:12
  • @nnnnnn: Especially here, where eval seems to be used to prevent static code analysis which would allow the garbage collector to collect largeStr even when a reference to the returned function is alive. – Bergi Commented Aug 6, 2013 at 22:27
  • @MedicineMan: Did you "understand what references are retained in the closure" or not? – Bergi Commented Aug 6, 2013 at 22:29
  • 1 @Bergi - So in this case eval() is the right tool for the job, if the job is to deliberately tie-up memory. (As long as people understand that the lesson is "be careful with closures", not "eval() always ties up memory".) – nnnnnn Commented Aug 6, 2013 at 22:32
  • @nnnnnn: Which is, I fear, what many people would do. It should better be replaced by something like if (false) return largeStr; – Bergi Commented Aug 6, 2013 at 22:36
Add a ment  | 

3 Answers 3

Reset to default 7

If instead of returning a function that does

    eval('');

you returned one that passes its argument

    eval(n);

then someone could call a('largeStr') to get the array, so the JavaScript interpreter cannot garbage collect the array.

Interpreters could realize that

eval('');

is equivalent to

;

but most are not smart enough to do that, so as soon as they see eval they stop allowing GC of closed-over variables as long as the closure is reachable.


The memory leak arises when eval can't effectively access closed-over variables because of the nature of its input:

eval('x' + (n-1));

Since 'x' + (n-1) can't produce a string of JS that references largeStr no input can lead to largeStr being used but it is still pinned in memory.


To see the whole thing in action, play around with

 var f = (function () {
     var a = [,,,,,];
     return function (x) { return eval(x); };
   })();
 alert(f('a.length'));

Okay let's consider what happens here;

var a = (function () { // `a` will be set to the return of this function
   var smallStr = 'x',
   largeStr = new Array(1000000).join('x'); 

   return function (n) { // which is another function; creating a closure
        eval('');
        return smallStr; 
   }; 
}());

The inner function needs to be able to access all variables from the outer function, meaning as long as a reference to it exists, variables from the outer function can't be garbage collected and hence continue consuming memory after it has finished invoking and therefore may result in "memory leaks".

If you're dealing with large data like this and you're finished with it, set it to null

Let's rewrite the code just a little bit:

function makeClosure() { 
  var smallStr = 'x',
  largeStr = new Array(1000000).join('x'); 

   return function (n) { 
        eval(''); //maintains reference to largeStr
       return smallStr; 
  }; 
}

var a = makeClosure();
assert(a() === 'x');

makeClosure returns a function, hence a is a function. However, that function still executes in the scope where it was defined (that's the definition of a closure). If you were to do:

function makeEnumerator() {
  var count = 0;

  return function () {
    count++;
    return count;
  };
}

var enum = makeEnumerator();
assert(enum() === 1);
assert(enum() === 2);

enum still has access to count. Back to our case, the closure keeps a reference to smallStr, which stays in memory. largeStr is not retained and should be freed.

However, eval also executes in the current scope and might use largeStr. Because of that, the browser is forced to keep largeStr as well.

Long story short, don't use eval :)

本文标签: javascriptHow do closures create memory leaksStack Overflow