Adam
Adam

Reputation: 3093

Why is this a memory leak in JavaScript?

I was reading this article ( http://www.ibm.com/developerworks/web/library/wa-memleak/ ) on IBM's website about memory leaks in JavaScript when I came across a memory leak that didn't quite look liked it leaked:

<html>
<body>
<script type="text/javascript">
document.write("Program to illustrate memory leak via closure");
window.onload=function outerFunction(){
   var obj = document.getElementById("element");
   obj.onclick=function innerFunction(){
      alert("Hi! I will leak");
   };
   obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
   // This is used to make the leak significant
};
</script>
<button id="element">Click Me</button>
</body>
</html>

I understood all the other leaks but this one stood out. It says there's a circular reference between the DOM and JavaScript object but I don't see it.

Can anyone shed some light on this?

EDIT: The link seems to have been taken down (I even refreshed the page I had up and it was down). Here's Google's cache (for as long as that lasts: http://webcache.googleusercontent.com/search?q=cache:kLR-FJUeKv0J:www.ibm.com/developerworks/web/library/wa-memleak/+memory+management+in+javascript&cd=1&hl=en&ct=clnk&gl=us&client=firefox-a )

Upvotes: 6

Views: 1647

Answers (2)

jfriend00
jfriend00

Reputation: 707996

The assignment to onclick of the innerFunction creates a function closure that preserves the value of the variables that are in scope of innerFunction. This allows the code in innerFunction to reference variables above it and is a desirable feature of javascript (something some other languages don't have).

Those variables that are in scope of innerFunction include obj. So, as long as there is a reference to this closure, the variables in that closure are preserved. It's a one-time piece of memory usage so it doesn't accumulate over time and thus isn't usually significant. But, if you put big data into one of those variables, then and expected it to be freed, then "yes" you would be using more browser memory than you expected.

Where this can cause problems is in this particular example, you have a circular reference between JS <==> DOM. In JS, you have preserved (in the function closure) a reference to the DOM object in the obj variable. In the DOM object, you have preserved a reference to the JS code and the function closure with the assignment to the onclick attribute. Some older browsers are dumb enough that even if you remove the "element" object from the DOM, the circular reference will keep the garbage collector from ever freeing the memory. This is not a problem in modern browsers as they are smart enough to see this circular reference and still free the object if there are no outside references to it.

In your particular code, you haven't actually created a leak because the element is still in the DOM. bigString is just a big chunk of data you've attached to the DOM element and it will stay there until you remove that attribute or remove the DOM object. That's not a leak, that's just storage.

The only way this would become a leak in IE6 is if you did this:

<html>
<body>
<script type="text/javascript">
document.write("Program to illustrate memory leak via closure");
window.onload=function outerFunction(){
   var obj = document.getElementById("element");
   obj.onclick=function innerFunction(){
      alert("Hi! I will leak");
   };
   obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
   // This is used to make the leak significant

};

// called later in your code
function freeObject() {
   var obj = document.getElementById("element");
   obj.parentNode.removeChild(obj);  // remove object from DOM
}

</script>
<button id="element">Click Me</button>
</body>
</html>

Now, you've removed the object from the DOM (sometime later in your code) and probably expected all memory associated with it to be freed, but the circular reference keeps that from happening in IE6. You could work-around that by doing the following:

<html>
<body>
<script type="text/javascript">
document.write("Program to illustrate memory leak via closure");
window.onload=function outerFunction(){
   var obj = document.getElementById("element");
   obj.onclick=function innerFunction(){
      alert("Hi! I will leak");
   };
   obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
   // This is used to make the leak significant

};

// called later in your code
function freeObject() {
   var obj = document.getElementById("element");
   obj.onclick=null;                 // clear handler and closure reference
   obj.parentNode.removeChild(obj);  // remove object from DOM
}
</script>
<button id="element">Click Me</button>
</body>
</html>

or, if you don't need the obj reference in the closure, you could null the reference from the closure entirely with this:

<html>
<body>
<script type="text/javascript">
document.write("Program to illustrate memory leak via closure");
window.onload=function outerFunction(){
   var obj = document.getElementById("element");
   obj.onclick=function innerFunction(){
      alert("Hi! I will leak");
   };
   obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
   // This is used to make the leak significant
   obj = null;   // kills the circular reference, obj will be null if you access if from innerFunction()
};
</script>
<button id="element">Click Me</button>
</body>
</html>

In practice, this is only a meaningful issue in a few cases when the memory usage of a leak could be significant.

  1. If you have large chunks of data that you store as DOM attributes, then a single object leak could leak a large amount of data. That's usually solved by not storing large chunks of data on DOM objects. Store them in JS where you control the lifetime explicitly.
  2. If you have a long lived page with lots of javascript interaction and an operation that creates/destroys DOM objects can be done many, many times. For example, a slideshow that runs unattended may be creating/destroying DOM objects over and over and over again. Even small amounts of memory leakage per item could eventually add up and cause a problem. In a slideshow I wrote, I just made sure that I didn't put any custom attributes on these particular DOM objects that I was add/removing and that all event handlers were removed before removing the object from the DOM. This should assure that there could be no circular references to them.
  3. Any kind of DOM operation you're doing over and over in a big loop.

Upvotes: 6

SLaks
SLaks

Reputation: 888047

This leaks memory due to an IE6 bug.

obj references innerFunction from onclick, and innerFunction references obj because it captures obj from the outer scope.

Upvotes: 0

Related Questions