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.
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.
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 referencesvar obj = document.getElementById("someLeakingDIV"); document.getElementById("someLeakingDiv").expandoProperty = obj;
To resolve the issue, explicitly set the
expandoPropertyto 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.
<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
DIVis being referenced twice. One of the references is held by the closure (the anonymous function assigned to theonclickevent) and can't be deleted even if you remove the node. If your application removes theelementnode 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
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
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
Finding leaking nodes with sIEve
Use the following steps to detect leaking nodes.
- Launch sIEve with the URL of your web application.
- Click Scan Now to find all DOM nodes in use in the current document (optional).
- 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.
- Take some actions on your web application that test whether there are leaks.
- Click Scan Now to refresh the DOM nodes in use (optional).
- 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.
- Analyze the report and review your code.
- 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.
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
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
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:
attachEventanddetachEvent - For other browsers:
addEventListenerandremoveEventListener
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)
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>”;
|
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.
| Description | Name | Size | Download method |
|---|---|---|---|
| Source code for this article | MyWidget.zip | 1KB | HTTP |
Information about download methods
Learn
- Read "Understanding and Solving Internet Explorer Leak Patterns" (MSDN, Jun 2005) to learn more about spotting IE memory leak patterns.
- "Memory leak patterns in JavaScript"
(developerWorks, Apr 2007), available both in English and in Chinese, covers the basics of circular references in JavaScript
and explains why they can cause problems in certain browsers, especially
when combined with closures.
- "Memory Leakage in Internet Explorer - revisited" (The Code
Project, Nov 2005) reviews IE leak patterns from a slightly different
perspective.
- Learn more about sIEve.
- Read about
IE
.innerHTML leaks and solutions.
- The developerWorks Web development zone
specializes in articles covering various web-based solutions.
- To listen to interesting interviews and
discussions for software developers, check out developerWorks podcasts.
- Stay current with developerWorks technical events and webcasts.
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
- Create your developerWorks profile today and set up a watch list on JavaScript and Dojo. Get connected and
stay connected with developerWorks community.
- Find other developerWorks members interested in web development.
- Share what you know: Join one of our developerWorks groups focused on web
topics.
- Roland Barcia talks about Web 2.0 and middleware in his blog.
- Follow developerWorks' members' shared bookmarks on web topics.
- Get answers quickly: Visit the Web 2.0 Apps forum.
- Get answers quickly: Visit the Ajax forum.




