Telerik blogs

This post is number two in a three part series about memory consumption for web applications in general. Read the first part

Memory leak examples

In this post, we will look at different examples of leaks. The examples presented are as close as possible to real situations. Their purpose is to present several patterns that usually cause memory leaks. A way to manage the leakage is given along with the examples. The examples rely on the use of the ASP.NET AJAX Framework.

Let’s start with a simple example of attaching a click event to a button element:

Attaching event handlers

HTML:

<form id="form1" runat="server">
       <asp:ScriptManager ID="theScriptManager" runat="server" />
       <div id="buttonContainer">
              <button id="button1">
                     Leak
              </button>
       </div>
</form>

JavaScript:

function pageLoad()
{
       $addHandler($get("button1"), "click", causeLeak);
}
function causeLeak()
{
       var container = $get("buttonContainer");
       container.innerHTML = "";
}

Open sIEve and run the example. Click the button and then use the about:blank button to redirect away of the page. Notice that the leak has been captured. If the emptying of the container is skipped, then there is no leak. The button element itself is not released as a resource when one tries to empty its container, because of the circular reference created by the Ajax Toolkit method - $addHandler.

The way to escape this leak one needs to remove the event handler and thus provide a leak free environment for the emptying of the container.

function causeLeak()
{
       var container = $get("buttonContainer");
       //preventing the leak of the button element
       $removeHandler($get("button1"), "click", causeLeak);
       container.innerHTML = "";
}

One important note from this example is to always release the event handlers, when they are not needed anymore or when the element itself should be removed.

Closures

Let’s elaborate the example a little bit. Let’s introduce closures into the attaching of the event. First, just to note that closures are a great pattern in JavaScript and its use has proven helpful in numerous cases. Here is an article on the topic that is useful for every web developer - http://jibbering.com/faq/notes/closures/. Back to the example:

function pageLoad()
{
       $addHandler($get("button1"), "click", function()
       {
              causeLeak();
       });
}

Now the memory leak should be back. There reason for this is, because the $removeHandler is unable to do its job. The event handler methods are actually different, although they do one and the same this. In the $addHandler method the handler is an anonymous function. In order to send it to the $removeHandler method we need to have a reference to it. Here is the code that prevents the memory leak:

var buttonHandler;
function pageLoad()
{
       buttonHandler = function()
       {
              causeLeak();
       };
       $addHandler($get("button1"), "click", buttonHandler);
}
function causeLeak()
{
       var container = $get("buttonContainer");
       //preventing the leak of the button element
       $removeHandler($get("button1"), "click", buttonHandler);
       container.innerHTML = "";
}

The conclusion here is that one needs to make sure that $removeHandler gets as parameters the same instances that previously the $addHandler as received.

Dynamically created DOM elements and JavaScript objects

Being a prototypical programming language JavaScript allows the creation of pseudo classes and instantiating objects. Here is an example of dynamically creating DOM elements and JS objects and the possible memory leaks that may arise from their use:

HTML:

<form id="form1" runat="server">
       <asp:ScriptManager ID="theScriptManager" runat="server" />
       <div id="buttonContainer"></div>
       <button id="leaker">Leak!</button>
       <button id="cleanUp">Clean Up</button>
</form>

JavaScript:

var objContainer = [];
function pageLoad()
{
       $addHandler($get("leaker"), "click", causeLeak);
       $addHandler($get("cleanUp"), "click", function()
       {
              Array.forEach(objContainer, function(obj) { obj.dispose(); });
       });
}
function causeLeak()
{
       var container = $get("buttonContainer");
              container.innerHTML = "";
              createButton(container);
}
 
function createButton(container)
{
       var button = document.createElement("button");
       button.innerHTML = "Simple button";
       container.appendChild(button);
       objContainer.push(new LeakClass(button));
}
 
function LeakClass(button)
{
       this.button = button;
       this.buttonHandler = Function.createDelegate(this, this.handleButtonClick);
       $addHandler(this.button, "click", this.buttonHandler);
}
LeakClass.prototype =
{
       handleButtonClick: function()
       {
             
       },
       dispose: function()
       {
              $removeHandler(this.button, "click", this.buttonHandler);
              delete this.button;
              delete this.buttonHandler;
       }
}

When running this example one will notice that the first click on the “Leak!” button doesn’t produce a memory leak. This is because the buttonContainer is actually empty, no button has been included previously. The second and all subsequent clicks however leak, because they release the content of an element without making sure that within the content all elements’ event handlers have been removed. This resembles the first leak pattern presented.

In order to deal with this memory leak situation, the dispose method was created. Within it the event handler for the button is removed. Also, it is a good practice to remove all other unnecessary references in order to ease the release of the memory and resources. The “cleanUp” button invokes iteration through an array with the JavaScript objects and makes sure that their dispose method is called. Thus the event handler reference is removed the element is prepared to be garbage-collected. Using a button here is only for the purpose of showing the differences between the two cases. Best practice would be to attach to the onunload event and do the dispose of resources there.

Memory leaks in IFRAMEs

Memory leaks in IFRAMEs are often unexpected and have an irrational behavior. A piece of code that doesn’t produce memory leaks in a normal environment, once put in a frame or an iframe, may result in multiple memory leaks. The reasons for their occurrence in that special case may be numerous and often are not easy to detect. Still they resemble the same memory leak patterns, but may occur in various code patterns. A curious example is an issue in the jQuery library - http://bugs.jquery.com/ticket/8863.

Click here to download a set of memory leaks examples that include the code from this post. The next post will show some best practices to optimize the memory consumption in dynamic web applications.

Continue to Part 3.

About the Author

Nikodim Lazarov

is a Senior Software Developer in the Telerik's ASP.NET AJAX devision. He has years of experience building web applications of various scales. Niko is a strong advocate of Web standards, TDD, Agile and basically anything that makes every developer a true craftsman. Outside of office he usually rides his bike, goes kayaking or is simply out with friends.

Related Posts

Comments

Comments are disabled in preview mode.