Find and resolve browser memory leaks caused by JavaScript and Dojo

Scan and sift with sIEve

If you're developing Web 2.0 applications that heavily use JavaScript and Ajax technologies, it is likely you will encounter browser memory leaks. The issue can be significant if you have a one-page application, or if a page handles a lot of UI operations. In this article, learn how to detect and correct memory leaks with the sIEve tool. Practical examples of memory leak issues, and the solutions, are included.

Yi Ming Huang, Software Engineer, IBM

Yi Ming Huang은 소프트웨어 엔지니어로 China Development Lab에서 Lotus ActiveInsight를 담당하고 있다. 그는 Portlet/Widget 관련 웹 개발에 참여했으며 REST, OSGi 및 Spring 기술에 관심을 갖고 있다.



05 April 2011

Also available in Chinese Japanese

Introduction

Develop skills on this topic

This content is part of a progressive knowledge path for advancing your skills. See Get started with Dojo development

Generally, browser memory leaks are not an issue for web applications. Users navigate between pages, and each page switch causes the browser to refresh. Even if there's a memory leak on one page, the leak is released after a page switch. The size of the leak is minor, and consequently often ignored.

Memory leaks became more of a problem when the Ajax technology was introduced. On a Web 2.0 style page, users don't refresh the page very often. Ajax technology is used to update the page content asynchronously. In an extreme scenario, the whole web application is constructed on one page. In this case, leaks accumulate and can't be ignored.

In this article, learn how memory leaks occur, and how to find the source of your leaks with sIEve. Practical examples of problems and solutions help you explore the issues. You can download the source code for the examples in this article.

Experience with JavaScript and the Dojo Toolkit is helpful but not required for an understanding of this article.


Leak patterns

As web developers know, Internet Explorer (IE) is different from Firefox and other browsers. In this article, the memory leak patterns and issues discussed are mainly targeted at, but not limited to, IE. Good practices should be applicable to all browsers.

A discussion of how IE manages memory is outside the scope of this article, but Resources has more information.

Due to the nature of JavaScript and browser memory management for JavaScript and DOM objects, carelessly coded JavaScript causes browser memory leaks. Two well-known patterns cause these leaks.

Circular references
Circular references are the root cause of nearly every leak. Generally, IE can handle the circular references and dispose of them correctly in the JavaScript world. The exception occurs when DOM objects are introduced. Circular references occur and cause the DOM node to leak when the JavaScript object holds the reference to the DOM element, and the DOM element's property holds the JavaScript object. Listing 1 shows a code sample that's often used to demonstrate this problem in articles about memory leaks.
Listing 1. Leak by circular references
var obj = document.getElementById("someLeakingDIV");
document.getElementById("someLeakingDiv").expandoProperty = obj;

To resolve the issue, explicitly set the expandoProperty to null when you're ready to remove the node from the document.

Closures
Closures cause memory leaks because they create circular references without being aware of it. The parent function's variable will be held as long as the closure is alive. Its life cycle goes beyond the function scope, which will cause a leak if not handled carefully. Listing 2 shows a leak caused by closure, which is a common coding style in JavaScript.
Listing 2. Closure that leaks
         <html>
<head>
<script type="text/javascript">
window.onload = function() {
    var obj = document.getElementById("element");
    // this creates a closure over "element"
    // and will leak if not handled properly.
    obj.onclick = function(evt) {
        alert("leak the element DIV");
    };
};
</script>
</head>
<body>
<div id="element">Leaking DIV</div>
</body>
</html>

If you use sIEve—a tool that detects orphan nodes and memory leaks—you'll notice that the element DIV is being referenced twice. One of the references is held by the closure (the anonymous function assigned to the onclick event) and can't be deleted even if you remove the node. If your application removes the element node later, the JavaScript reference will still hold an orphan node. That orphan node will cause the memory leak.

Understanding why closures create circular references is important. A diagram in the article "Memory Leakage in Internet Explorer - revisited" illustrates the issue clearly, and is shown in Figure 1.

One way to fix the problem is to remove the closure.

Figure 1. Closure that creates the circular reference between DOM and JavaScript
Closure that creates the circular reference between DOM and JavaScript

Introducing sIEve

sIEve is a tool that helps detect memory leaks. You can download sIEve and access documentation from Resources. The main sIEve window is shown in Figure 2.

Figure 2. sIEve main window
sIEve main window

The tool is especially useful once you click Show in use. You will see all the DOM nodes in use, including the orphan nodes and increased/decreased references to DOM nodes.

Figure 3 shows a sample view. The causes of the leak are:

  • Orphan nodes, marked with Yes in the Orphan column.
  • Incorrectly increased references, in blue, to the DOM nodes.

Use sIEve to find the leaking nodes and review the code that fixes them.

Figure 3. sIEve: DOM node in use
sIEve: DOM node in use

Finding leaking nodes with sIEve

Use the following steps to detect leaking nodes.

  1. Launch sIEve with the URL of your web application.
  2. Click Scan Now to find all DOM nodes in use in the current document (optional).
  3. Click Show in use to see all the DOM nodes. At this point, all the nodes will be in red (new items) because you just started.
  4. Take some actions on your web application that test whether there are leaks.
  5. Click Scan Now to refresh the DOM nodes in use (optional).
  6. Click Show in use. Now the view contains some interesting information. Orphan nodes can be found, or an unexpected reference to a certain DOM node can be increased.
  7. Analyze the report and review your code.
  8. Repeat steps 4-8 as necessary.

sIEve can't find all leaks for your application, but it will find leaks caused by orphan nodes. The additional information, such as ID and outerHTML, can help you identify the leaking nodes. Review your code that manipulates the leaking nodes and make changes accordingly.


Practical examples

This section contains more examples of conditions that can cause memory leaks. The samples and best practices are based on the Dojo toolkit, but most of the examples are valid in general JavaScript programming.

When cleaning up, a common practice is to remove the DOM and delete the JavaScript object to avoid memory leaks. There's more to it, though. The rest of this section builds on the patterns previously introduced.

The following example includes a site that you can create. You'll also delete a web widget from a page. The actions are performed on the single page with no page refresh. Listing 3 shows the widget (not dijit) defined in a Dojo class that will be enhanced gradually in the rest of this article.

Listing 3. MyWidget class
dojo.declare("leak.sample.MyWidget", null, {
	constructor: function(container) {
		this.container = container;
		this.ID = dojox.uuid.generateRandomUuid();
		this.domNode = dojo.create("DIV", {id: this.ID, 
			innerHTML: "MyWidget "+this.ID}, this.container);
	},
	destroy: function() {
		this.container.removeChild(dojo.byId(this.ID));
	}
});

Listing 4 shows the main page that manipulates the widgets.

Listing 4. The site HTML
<html>
<head>
<title>Dojo Memory Leak Sample</title>
<script type="text/javascript" src="js/dojo/dojo/dojo.js"></script>
<script type="text/javascript">
dojo.registerModulePath("leak.sample", "../../leak/sample");
dojo.require("leak.sample.MyWidget");

widgetArray = [];

function createWidget() {
	var container = dojo.byId("widgetContainer");
	var widget = new leak.sample.MyWidget(container);
	widgetArray.push(widget);
}
function removeWidget() {
	var widget = widgetArray.pop();
	widget.destroy();
}
</script>
</head>
<body>
	<button onclick="createWidget()">Create Widget</button>
	<button onclick="removeWidget()">Remove Widget</button>
	<div id="widgetContainer"></div>
</body>
</html>

Using dojo.destroy() or dojo.empty()

At first glance, the issue does not seem problematic. Widgets are created and stored in an array. They pop up from the array and are removed. DOM nodes are also detached from the document. But if you use sIEve to trace the difference between the create widget and remove widget actions, you'll find that every time a widget node becomes an orphan it can entail a memory leak. Figure 4 shows an example of creating and removing widgets twice.

Figure 4. Leaks for the widget nodes
Leaks for the widget nodes

This situation is possibly an IE bug. Even if you create an element and attach it to the document, and then remove it with parentNode.removeChild() immediately, the orphan node will still exist.

You can use dojo.destroy() or dojo.empty() to clear the DOM node. Dojo implemented dojo.destroy(<domNode>) to move the deleted nodes somewhere else and then destroy them. It will create a node for that kind of garbage collection. The nodes you intended to delete are removed. (See the Dojo source code for implementation details.) Listing 5 shows how to fix the problem.

Listing 5. Using dojo.destroy() to remove DOM nodes
## change the destroy() method of MyWidget.js
destroy: function() {
	dojo.destroy(dojo.byId(this.ID));
}

When using sIEve for verification, you'll find that the first time you remove a widget, Dojo creates an empty DIV (the garbage). In subsequent adding and removing, no DOM node will be an orphan, so a leak will not occur.

Nullifying the JavaScript reference to a DOM node

It's a good practice to nullify the JavaScript reference to a DOM node when doing clean-up. In Listing 3, the destroy method doesn't nullify the JavaScript references to the DOM node (this.domNode, this.container). In most cases, this situation won't cause memory leaks, but when you're working on a more complex application where other objects hold the references to your widgets, a problem can arise.

Assume another repository that you don't know about is available and holding the references to your widgets, and that, for some reason, it can't be cleaned. Removing the widget will cause the DOM node referenced by it to be orphaned. Listing 6 shows the changes.

Listing 6. Site HTML: Add one more object (widgetRepo) to hold the widgets
widgetArray = [];
widgetRepo = {};

function createWidget() {
	var container = dojo.byId("widgetContainer");
	var widget = new leak.sample.MyWidget(container);
	widgetArray.push(widget);
	widgetRepo[widget.ID] = widget;
}

Now try to add and remove the widget, and then use sIEve to detect a memory leak. Figure 5 shows the orphan nodes for the widget DIV and increased references to the widgetContainer DIV. In the Refs column, the widgetContainer DIV should have only one reference in the document.

Figure 5. Orphan nodes
Orphan nodes

The solution is to nullify the DOM node references during clean-up, as shown in Listing 7. It is considered a good practice to add these nullify statements when possible since it won't harm your original function.

Listing 7. Nullify the DOM references
## the destroy method of MyWidget class
destroy: function() {
	dojo.destroy(dojo.byId(this.ID));
	this.domNode = null;
	this.container = null;
}

Disconnecting events and unsubscribing topics

With Dojo, another good practice to avoid memory leaks is to disconnect the events you connected and unsubscribe the topics you subscribed. Listing 8 shows an example of connecting and disconnecting events.

With JavaScript programming, it is generally recommended that you disconnect events for DOM nodes before removing them from a document. Use the following APIs to connect and disconnect events on different browsers.

  • For IE: attachEvent and detachEvent
  • For other browsers: addEventListener and removeEventListener
Listing 8. Dojo.connect and dojo.disconnect
## the constructor method of MyWidget class
constructor: function(container) {
	// … old code here	
	this.clickHandler = dojo.connect(
	this.domNode, "click", this, "onNodeClick");
}

## the destroy method of MyWidget class
destroy: function() {
	// … old code here
	dojo.disconnect(this.clickHandler);
}

You can also set up connections between components in Dojo by subscribing and publishing topics. It is implemented as the Observer pattern. In this case, a best practice is to unsubscribe the topic when doing clean-up to avoid memory leaks. Use the following API for the two methods:

  • dojo.subscribe(/*string*/topic, /*function*/function)
  • dojo.unsubscribe(/*string*/topic)

Setting innerHTML

IE memory leaks can be caused if you aren't careful with how you set innerHTML with JavaScript. (See Resources for details.) Listing 9 shows a scenario that can cause IE memory leaks.

Listing 9. innerHTML leak on IE
// 1. An orphan node should be in the document
var elem = document.createElement(“DIV”);

// 2. Set the node’s innerHTML with an DOM 0 event wired
elem.innerHTML = “<a onclick=’alert(1)’>leak</a>”;

// 3. Attach the orphan node to the document
document.body.appendChild(elem);

The type of code shown above is common in Web 2.0 applications, so tread carefully. The solution is to make sure the node is not an orphan before setting the innerHTML. Listing 10 shows the fix to the code from Listing 9.

Listing 10. Fix for innerHTML leak
var elem = document.createElement(“DIV”);

// now the node is not orphan anymore
document.body.appendChild(elem);

elem.innerHTML = “<a onclick=’alert(1)’>no leak</a>”;

Conclusion

It is relatively easy to identify the pattern that causes browser memory leaks. Finding the source of the issue in your application source code can be more difficult. sIEve can help you find most of the leaks caused by orphan nodes. This article explained how memory leaks can occur with just a little bit of careless JavaScript. The best practices outlined in this article can help you prevent leaks from occurring.


Download

DescriptionNameSize
Source code for this articleMyWidget.zip1KB

Resources

Learn

Get products and technologies

  • Download sIEve, the memory leak detector for Internet Explorer.
  • Innovate your next development project with IBM trial software, available for download or on DVD.

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Web development on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development
ArticleID=644873
ArticleTitle=Find and resolve browser memory leaks caused by JavaScript and Dojo
publish-date=04052011