JavaScript Memory Leak Detector

JavaScript Memory Leak Detector  is a debugging tool to detect memory leaks in JavaScript code. It can be installed as an Internet Explorer band and it is expressly designed to find memory leaks in JavaScript code that runs in Internet Explorer.

The tool is currently in beta and we welcome your feedback (email paolosev [at] microsoft [dot] com, or use the feedback form below).

As many web developers know, it is relatively easy to build a website which results in memory leaks when viewed in Internet Explorer. IE team members have written MSDN articles on leak patterns, and other sites have posted articles with varying tone, depending on the author’s frustration with the problem.

These memory leaks often occur as a result of circular references between JavaScript objects and objects within IE’s DOM (document object model).

Both the JavaScript engine and IE have independent memory management schemes. The JavaScript engine uses garbage collection to reclaim the memory allocated for JavaScript objects that are not used anymore.

Instead, an IE DOM object is a COM object; therefore its lifespan is ruled by a reference count. Both systems work perfectly in isolation but problems can arise when there are circular references between these two worlds as each side can’t see the entire cycle of these references.

For example, the following code (used to) generate a leak in IE6:<html>
    <head>
        <script language="JScript">
         var jsObject;
         function SetupLeak()
         {
                jsObject = document.getElementById("LeakedDiv");
                document.getElementById("LeakedDiv").expandoProperty = jsObject;
         }
     </script>
    </head>
    <body onload="SetupLeak()">
        <div id="LeakedDiv"></div>
    </body>
</html>

Here the SetupLeak() function creates a circular reference like this:

The IE team has been working hard to solve the problem. With the initial versions of Internet Explorer 6 practically all circular references between Javascript and DOM objects caused a memory leak. Avoiding leaks was not an easy task even because there are language constructs, like JavaScript closures, that make particularly difficult to track down those circular references.

Internet Explorer 7 improved the situation by releasing all references to JavaScript objects attached to the DOM tree when IE navigates away from that page. This allows the JavaScript engine to garbage collect those JavaScript objects and recover that memory. Almost the same changes were also shipped with the latest security updates of IE6. So, the previous code does not actually leak anymore.

However, as some web developers have pointed out, those changes did not solve the problem entirely. IE still leaves behind anything not attached to the tree when the markup is tore down. The previous script, for example, would still leak if the LeakedDiv object was detached from the markup of the page calling:

LeakedDiv.parentElement.removeChild(LeakedDiv);

before closing the page.

As the problem has already been thouroughly described elsewhere, I won’t go into more details here. Those interested in learning more can find more info in the papers listed in the references section.

The situation will definitely improve in future versions of IE.  In the meantime, however, it is useful to have a tool which helps to find potential leaks.


INSTALLATION

JavaScript Memory Leak Detector is an Internet Explorer Band. It works on Windows XP and Windows Vista, with Internet Explorer 7.

To install it:

  1. Download the installer and unpack it from the ZIP archive: download link
  2. Close all the running instances of Internet Explorer.
  3. Run the setup program jsleaksdetector.msi
  4. Enable the new Explorer Bar (in Internet Explorer 7) by selecting the command Tools->Toolbars->Explorer Bar and checking “js memory leaks detector“, as shown (use the Tools menu labeled with the gear icon):



    Note that in Windows XP SP2 the new Explorer Bar is also available in the View menu (View->Explorer Bar).
    In some cases a reboot might be required to make the leaks detector appear in the list of installed explorer bars. We are now working to reproduce and fix this issue.
  5. Enable script debugging by selecting Tools->Internet Options->Advanced->Browsing and unchecking Disable script debugging, as shown:

Now a new explorer band will appear at the bottom of Internet Explorer. The tool is now ready to detect memory leaks, not on the current page, however, but beginning from the next page loaded in the browser.


USAGE

This band is composed of four windows:

  1. Document tree window
  2. Memory leaks window
  3. Call stack window
  4. Script window

Window #1: Document Tree

The leaks detector works intercepting the execution of JavaScript inside IE. The tree view at the left of the band contains a list of all the documents previously opened in the browser and, for each document shows all the script code that was loaded into a script engine.

Color Codes

The tree uses a color code to represent the state of an HTML document.

The document currently loaded in the current tab of IE is displayed in blue. The tool can detect a document’s memory leaks only after the document has been unloaded. Therefore, expanding the ‘blue’ document node we can see the list of scripts ready for execution, but no leaks yet.

As soon a document is closed (for example refreshing the page or navigating to another page) the system calculates a list of possible leaked DOM objects. If this list is empty, the color of the document is changed to green. If one or more leaks are detected, the color is changed to red.

Script Nodes

Script nodes represent script files or script snippets loaded by the current document. Selecting a script node shows the actual content of the script in the script view.

Leak Nodes 

Leak nodes represent DOM object that were (possibly) leaked after the execution of the scripts in the page. Whenever possible the DOM objects are shown in the markup tree, in the position that they occupy at creation time. However, leaked objects in IE7 are usually created dynamically, and then dynamically inserted into the DOM tree. Dynamically created and then leaked objects are just listed as children of the ‘leaks’ node.

Window #2: Memory Leaks

A JavaScript memory leak is (almost) always caused by a circular reference from an IE DOM object and a JavaScript object. When a leak node is selected in the tree, this list shows a list of “attached” JavaScript objects whose circular reference could be the cause of the leak.

Window #3: Call Stack

Selecting one of the existing items in the previous Memory Leaks window causes a call stack to be dispayed in the Call Stack window. The call stack represents the state of the script at the moment when the JavaScript object was attached to the DOM object.

For example, in our sample script, the call stack would show the function calls that led to the line:

document.getElementById("LeakedDiv").expandoProperty = jsObject;

as this was the statement that caused the leak.

Window #4: Script

The Script Window serves two purposes:

  1. When a script node is selected in the Document tree, the Script window shows the script code.
  2. When an item is selected in the Memory leaks window, the Script window displays the script where (possibly) the memory leak originated, highlighting the statement that attached the JS object to the DOM object.

Together, the four windows that compose the IE band should provide enough debugging information to allow a developer to detect and analyze possible memory leaks.


LEAK DETECTION HEURISTICS

The header of the script window also contains a toolbar with two buttons. The “About” button just displays the current version of the DLL. More interesting is the “Settings” button, which shows the following dialog:

This dialog allows the user to select one of three available heuristics to detect JavaScript leaks. To understand the difference between the three available options is useful to take a look at the internal workings of the tool.

How it works

Like any IE Band, the JavaScript Memory Leak Detector is a COM in-process DLL loaded in the Internet Explorer process. The fact of living inside the IE process allows it to easily intercept some of the API calls made by the IE code. In this case, we are interested in intercepting every call that creates a Jscript engine.

The Jscript engine is a COM object, and it is instantiated by Trident (mshtml.dll) with a call to CoCreateInstance(). Therefore, the first operation made by the tool will be to intercept the calls to CoCreateInstance made by the mshtml module. There are a few ways to implement this API hooking; in this case the simple technique of overwriting the module Import Table in memory works perfectly. (See Robbins’ “Debugging Applications” for more details).

At this point we can substitute our own Jscript engine in place of the real engine. By implementing all the ActiveScript interfaces exposed by a Jscript engine and delegating all the calls to an instance of the real Jscript engine, the tool can transparently intercept all the interactions between Trident and Javascript and still have Internet Explorer to run correctly.

Now, a Jscript engine by itself has no notions of Internet Explorer and its DOM objects. It is IE that registers the root (window) object to the engine and loads into the engine all the scripts contained or loaded by a HTML document. Since we are intercepting all the calls to Jscript, we can thus have a reference to all the DOM objects that are passed to or used by a Javascript function.

The technique to do this is a bit tricky. A DOM object is passed to (and accessed by) Jscript through an IDispatch interface; so for each new object that we meet we create a fake COM object that works as interceptor (or wrapper), exposing IDispatch and delegating the calls to the real (contained) IDispatch object.

Every time a method or property is called to a DOM element by JavaScript, the call is actually made to our wrapper and then delegated to the real object. The wrapper can analyse the method in/out parameters and return value, looking for other IDispatch pointers that represent new DOM objects. If it finds a new IDispatch pointer not yet met, we know that this object will now be visible to the JavaScript code, and we need to build another wrapper and pass it to JavaScript in its place. In the end, every JavaScript function will access DOM objects only through these wrappers and the tool will have complete control over the script execution.

Detecting leaks

Now that we can intercept all the calls between the DOM and the JavaScript worlds, we are on the good path towards leak detection. We can choose between different strategies, which correspond to the three options in the settings dialog:

IE6/IE7: look for circular references. If some DOM object is (a) maintained alive by a reference from the Jscript engine and (b) maintains a reference to one (or more) Jscript objects, then this DOM object could be leaked.

We cannot be sure, since Internet Explorer will try to break circular references and IE7 is quite successful in doing this. So, our heuristic will distinguish the cases based on the version of Internet Explorer, giving us the first two settings options:

  • IE6: where all circular references are considered leaks
  • IE7: where only the DOM objects detached by the markup at “closing” time could be leaked

Actual leaks: look for leaked DOM elements. If some DOM object has a positive ref count after the page has been completely unloaded, then it is probably leaked. (But even in this case, we cannot be definitely sure, as IE could still keep a reference to the object for some reason and release it later).

In all the three cases we follow a heuristic approach. We don’t know what IE will actually do when the page is unloaded and, as we have seen, the IE team is removing old leak patterns with every new version of the browser. Anyway, the list of leaks reported by the tool can be useful information to find potential leaks and to enforce best practices in JavaScript coding.

Leave a Reply

Your email address will not be published. Required fields are marked *