Monday, July 7, 2014

A Memory Leak in IE-9

I recently read David Glasser's blog-post "A surprising JavaScript memory leak found at Meteor" about a JavaScript memory-leak in Chrome. That blog-post is about a year old so I wanted to check if things might have improved since then.  I tried a simplified version of his example in IE-9 and surprisingly could reproduce the leak with even fewer statements.

Here's my modified, simplified version of David's example:
 
  var variableOfOuterScope = null;

 function runManyTimes() 

 {var previousValue    = variableOfOuterScope ;
  variableOfOuterScope 
  { bigArray:       new Array (2000000).join('*')
  , aRefToAFunction: function () {}
  };
  // previousValue = null;
  // Un-commenting the above line will fix the leak.
 };
  
 for (var i=0; i < 30 ; i++)
 { runManyTimes () ;  
 }
 // See in Windows Task Manager how the memory used  
 // by IE9 has grown significantly, to about 240 mb
 // after running the code above.


After running the code it seems the browser now uses about 240 mb instead of the 24 mb it used before running it. This is not good, memory is bursting at the seams! Why does it  happen? Why does the memory leak?  I assume it goes something like this:

When you call runManyTimes for the first time it creates a new "context" which the anonymous function inside it holds on to. This context refers to variables that exist outside the anonymous function, so that if it wanted to, it could refer to their values which were in effect at the time runManyTimes exited.  This context refers to the variable 'previousValue' whose value when runManyTimes returns first time is null. But the value of variableOfOuterScope now contains a very big Array. By the time you have called it the 2nd time,  previousValue points to that very big Array and  variableOfOuterScope then contains yet another big Array.

Each time you call runManyTimes  this chain of very big Arrays gets one bigger.  But if you un-comment the fix-line and set previousValue to null, this chain is broken, and  garbage gets collected.

One more thing is required to cause the leak to happen: The  variableOfOuterScope  must contain a REFERENCE to the anonymous function. If you just created a simple inner function without referring to it, there is no leak. Saving the anonymous function to a field in  variableOfOuterScope  means each of its values refers to the anonymous function (created on a specific call  of runManyTimes) which holds the context which refers to the previous value of  variableOfOuterScope and so on. Without that the variableOfOuterScope  would not refer to the function which holds on to the context, which refers to the previous value, which refers to the previous value and so on. But since it does, it now refers to the whole chain of the big arrays, thus consuming the memory.


Ah, but then, I ran the example on Chrome v. 31. There was no memory leak, even without setting previousValue to null!  Chrome is able to prevent the leak on its own. IE-9 is not. Maybe Chrome realizes that the anonymous function is not referring to any variables, so it need not create a "context" for it.

The moral of this story then is: Beware of memory-leaks in  JavaScript, when creating functions within functions.  It is fairly easy to detect the leaks by observing the amount of memory your browser consumes, when running your application. Don't worry too much about what causes them exactly, because results might differ on different browsers. Try to reset variables whose values you no longer need to null, and that may fix it.  Help the garbage-collector a bit!


 © 2014 Panu Viljamaa. All rights reserved

No comments:

Post a Comment