Tutorial: Finding a memory leak (JavaScript)

This topic leads you through the process of identifying and fixing a simple memory issue by using the JavaScript Memory Analyzer. In this tutorial, we create an app that generates a large array of data. We expect the app to dispose of the data when we navigate to a new page.

Note Note

The JavaScript Memory Analyzer is available for Windows 8 in Visual Studio 2012 Update 1.

Running the JavaScript Memory Analyzer test app

  1. In Visual Studio, click File > New > Project.

  2. Click JavaScript in the left pane, and then click Navigation App in the middle pane.

  3. In the Name box, type a name, like JS_Mem_Tester, and then click OK.

  4. In Solution Explorer, in the pages\home folder, open home.html and paste this code between the <body> tags:

        <div class="fragment homepage">
            <header aria-label="Header content" role="banner">
                <button class="win-backbutton" aria-label="Back" disabled type="button"></button>
                <h1 class="titlearea win-type-ellipsis">
                    <span class="pagetitle">Welcome to JSMemTester!</span>
                </h1>
            </header>
            <section aria-label="Main content" role="main">
              <p>Start generating data...</p>
              <button class="startButton" title="start" >Start</button>
              <p class="statusMsg1">""</p>
              <p>Navigate to page... (reload)</p>
              <button class="navButton" title="navigate" >Navigate</button>
            </section>
        </div>
    
  5. Open home.js and replace all the code with this code:

    (function () {
        "use strict";
    
        var data;
    
        WinJS.UI.Pages.define("/pages/home/home.html", {
            // This function is called whenever a user navigates to this page. It
            // populates the page elements with the app's data.
            ready: function (element, options) {
                // TODO: Initialize the page here.
    
                var firstElem = document.querySelector('.startButton');
                firstElem.addEventListener('click', sButtonClicked.bind(this));
    
                var secondElem = document.querySelector('.navButton');
                secondElem.addEventListener('click', nButtonClicked.bind(this));
    
            },
    
            generateData: function () {
                data = {};
                var x = 0;
                var newData = "1";
                for (var i = 0; i < 300; i++) {
                    data[i] = "data" + newData;
                    newData = newData + (100 * set[x]).toString();
                    if (i == 100) { x = 1; }
                    if (i == 200) { x = 2; }
                }
            }
        });
    
        function sButtonClicked(args) {
            this.generateData();
            var elem = document.querySelector('.statusMsg1');
            elem.textContent = "Done generating data (string array).";
        }
    
        function nButtonClicked(args) {
            WinJS.Navigation.navigate('/pages/home/home.html');
        }
    
        // Adding arbitrary values to sample data.
        var mod1 = 10;
        var mod2 = 100;
        var mod3 = 1000;
    
        var set = [mod1, mod2, mod3 ];
    
    })();
    
  6. Press F5 to start debugging. Verify that this page appears:

    Welcome screen of test app
  7. Switch back to Visual Studio (Alt+Tab) and press Shift+F5 to stop debugging.

    Now that we've verified that the app works, we can examine the memory usage.

Analyzing the memory usage

  1. On the Debug toolbar, click Simulator in the Start Debugging drop-down list.

    You can also click Local Machine or Remote Machine in this list. But if you use the simulator you can place it next to Visual Studio to make it easy to switch between the running app and the JavaScript Memory Analyzer. For more info, see Running Windows Store apps from Visual Studio and Running Windows Store apps on a remote machine.

  2. On the Debug menu, point to JavaScript Analysis, then Memory, and then click Launch Startup Project.

    In this tutorial, we're attaching the memory analyzer to the startup project. For info about other options, like attaching the memory analyzer to an installed app, see Analyzing memory usage data (JavaScript).

    When you start the memory analyzer, you might see a User Account Control requesting your permission to run VsEtwCollector.exe. Click Yes.

  3. From the running app, switch to Visual Studio (Alt+Tab).

    The JavaScript Memory Analyzer displays information in the Diagnostics hub tab.

    The memory graph in this summary view shows process memory usage over time. The view also provides commands like Take Heap Snapshot. A snapshot provides detailed information about memory usage at a particular time. For more info, see Analyzing memory usage data (JavaScript).

  4. Click Take Heap Snapshot.

  5. Switch to the app and click the Start button.

    The code in home.js generates a large array when you press Start. We'll use this for diagnostic purposes.

  6. Switch to Visual Studio and click Take Heap Snapshot again.

    This illustration shows the two snapshots in the lower pane of the summary view.

    Snapshot summaries
  7. You can compare the snapshots. Snapshot #2 shows the following:

    • The heap size (blue text, left side) has increased dramatically, to more than 1 MB.

    • The difference in heap size since the previous snapshot (red text, left side) is more than 400 KB.

    • The number of objects on the heap (blue text, right side) has increased by several hundred (to more than 3,900).

    • The difference in the number of objects on the heap (red text, right side) since the previous snapshot is more than 300 objects.

  8. In Snapshot #2, click the red text on the left, which shows a differential value like +404 KB.

    This opens a differential view, called Snapshot #2 - Snapshot #1, with the Dominators view showing by default. The following illustration shows this view.

    Snapshot differential in Dominators view

    In this view, you see a list of objects retaining memory, starting with the objects retaining the most memory. By default, the JavaScript Memory Analyzer filters out the built-in objects created by the Windows Runtime and Windows Library for JavaScript. This helps to focus the information on app-related code.

    You can see that the data object has a Retained Size Diff. value of more than 400 KB.

    Tip Tip

    When a desired object or identifier is hard to find, in some views you can type the identifier name in the Name filter box to find and select the particular identifier.

  9. In the identifiers list, right-click the data identifier, and then click Show in roots view.

    The selected value appears in the Roots view of the Snapshot #2 – Snapshot #1 differential view. The Roots view shows you where the object you're inspecting is referenced in relation to the Global object. This might help to identify where a memory problem is occurring. Here's a partial illustration of the Roots view at this time. (The Global object at the top of the tree is not visible.)

    Snapshot Diff in Roots View

    In the Roots view, we can see that the data variable is referenced by an anonymous function that's called by the home page's ready function, and that this is rooted to a DIV element that contains a winControl object. From our knowledge of the app, we know that this control object refers to the app's Start button.

  10. Switch to the app and click the Navigate button.

    The Navigate button navigates to a new page. (To keep this app simple, we just reload the home page.)

  11. Switch to Visual Studio and click Take Heap Snapshot.

    In Snapshot #3, you can see that the size of the heap and the number of objects on the heap haven't changed much since the previous snapshot. Here's what the snapshots look like:

    Snapshot after pressing the Navigate button

    For this tutorial, we expect that the data (the array) generated by the app after you press Start is disposed of when you press Navigate to go to a new page (or reload, in this case). The data isn't disposed of, however, so we'll fix that issue.

  12. Click the Stop button in the summary view.

Fixing a memory issue

  1. In home.js, remove the event handling code for the Navigate button:

        function nButtonClicked(args) {
            WinJS.Navigation.navigate('/pages/home/home.html');
        }
    

    Replace it with this code:

        function nButtonClicked(args) {
            data = null;
            WinJS.Navigation.navigate('/pages/home/home.html');
        }
    
  2. On the Debug menu, point to JavaScript Memory Analysis, and then click Launch Startup Project.

  3. Follow the same procedure as described in the previous section to take three snapshots. The steps are summarized here:

    1. Switch to Visual Studio and click Take Heap Snapshot.

    2. In the app, click the Start button.

    3. Switch to Visual Studio and click Take Heap Snapshot.

    4. In the app, click the Navigate button.

    5. Switch to Visual Studio and click Take Heap Snapshot.

    You can see in Snapshot #3 that the heap size is similar to the size of the heap before you pressed Start and generated data. Here are the snapshots:

    Snapshot with Memory Issue Fixed
  4. In Snapshot #3, click the blue text that shows the heap size on the left side.

    This opens the Dominators view for Snapshot #3. This is a detail view of Snapshot #3, not a differential view.

  5. In the Name filter box, type data.

    This time, no data variable is present in the heap. So the memory issue is fixed!

Show: