Record heap snapshots using the Memory tool

Use the heap profiler in the Memory tool to do the following:

  • Record JavaScript heap (JS heap) snapshots.
  • Analyze memory graphs.
  • Compare snapshots.
  • Find memory leaks.

The DevTools heap profiler shows the memory distribution used by the JavaScript objects and by related DOM nodes on the rendered webpage.

Take a snapshot

  1. Open the webpage you want to analyze. For example, open the Scattered objects demo page in a new window or tab.

  2. To open DevTools, right-click the webpage, and then select Inspect. Or, press Ctrl+Shift+I (Windows, Linux) or Command+Option+I (macOS). DevTools opens.

  3. In DevTools, on the Activity Bar, select the Memory tab. If that tab isn't visible, click the More tools (More tools icon) button.

  4. In the Select profiling type section, select the Heap snapshot option button.

  5. Under Select JavaScript VM instance, select the JavaScript VM that you want to profile.

  6. Click the Take snapshot button:

The Memory tool, the Heap snapshot option is selected, and the Take snapshot button is highlighted

After the newly recorded heap snapshot has been loaded into DevTools and has been parsed, the snapshot is displayed and a new entry appears in the Profiles sidebar under HEAP SNAPSHOTS:

Total size of reachable objects

The number below the new sidebar item shows the total size of the reachable JavaScript objects. To learn more about object sizes in the heap snapshot, see Object sizes and distances in Memory terminology.

The snapshot only displays the objects from the memory graph that are reachable from the global object. Taking a snapshot always starts with a garbage collection.

Take another snapshot

To take another snapshot when one is already displayed in the Memory tool, in the sidebar, click Profiles above the existing snapshot:

The Profiles button to take another snapshot

Clear snapshots

To clear all snapshots from the Memory tool, click the Clear all profiles (The clear icon) icon:

Remove snapshots

View snapshots

Heap snapshots can be viewed in multiple different ways in the Memory tool. Each way of viewing a heap snapshot in the UI corresponds to a different task:

View Content Use for
Summary Shows objects grouped by their constructor name. Finding objects, and the memory they use, based on types that are grouped by constructor name. Helpful for tracking down DOM leaks.
Comparison Displays the differences between two snapshots. Comparing two (or more) memory snapshots from before and after an operation. Inspecting the delta in freed memory and inspecting the reference count helps you confirm the presence and cause of a memory leak, and helps determine its cause.
Containment Allows exploration of heap contents. Provides a better view of object structure, helping analyze objects referenced in the global namespace (window) to find out what is keeping objects around. Use it to analyze closures and dive into your objects at a low level.

To switch between views, use the dropdown list at the top of the Memory tool:

Switch views selector

Note

Not all properties are stored on the JavaScript heap. Properties implemented using getters that run native code aren't captured. Also, non-string values such as numbers aren't captured.

Summary view

Initially, a heap snapshot opens in the Summary view, which displays a list of constructors:

Summary view

Each constructor in the list can be expanded to show the objects that were instantiated using that constructor.

For each constructor in the list, the Summary view also shows a number such as ×123, indicating the total number of objects created with the constructor. The Summary view also shows the following columns:

Column name Description
Distance Displays the distance to the root using the shortest simple path of nodes. See Distance in Memory terminology.
Shallow size Displays the sum of shallow sizes of all objects created by a certain constructor function. The shallow size is the size of the JavaScript heap that's directly held by an object. The shallow size of an object is usually small, because a JavaScript object often only stores its description of the object, not the values, in the object's directly held memory. Most JavaScript objects store their values in a backing store that's elsewhere in the JavaScript heap, and only expose a small wrapper object on the portion of the JavaScript heap that's directly owned by the object. See Shallow size in Memory terminology.
Retained size Displays the maximum retained size among the same set of objects. The size of memory that you can free after an object is deleted (and the dependents are made no longer reachable) is called the retained size. See Retained size in Memory terminology.

After expanding a constructor in the Summary view, all of the constructor's instances are displayed. For each instance, the shallow and retained sizes are displayed in the corresponding columns. The number after the @ character is the unique ID of the object, allowing you to compare heap snapshots on per-object basis.

Constructor entries in the Summary view

The Summary view in the Memory tool lists object constructor groups:

Constructor groups

The constructor groups in the Summary view might be built-in functions such as Array or Object or they might be functions defined in your own code.

To reveal the list of objects that were instantiated by a given constructor, expand the constructor group.

Special category names in the Summary view

The Summary view also contains special category names that aren't based on constructors. These special categories are:

Category name Description
(array) Various internal array-like objects that don't directly correspond to objects visible from JavaScript, such as the contents of JavaScript arrays, or the named properties of JavaScript objects.
(compiled code) Internal data that V8 (Microsoft Edge's JavaScript engine) needs to run functions defined by JavaScript or WebAssembly. V8 automatically manages memory usage in this category: if a function runs many times, V8 uses more memory for that function so that the function runs faster. If a function hasn't run in a while, V8 might delete the internal data for that function.
(concatenated string) When two strings are concatenated together, such as when using the JavaScript + operator, V8 might choose to represent the result internally as a concatenated string. Rather than copying all of the characters of the two strings into a new string, V8 creates a small object which points to the two strings.
InternalNode Objects allocated outside of V8, such as C++ objects defined by Blink, Microsoft Edge's rendering engine.
(object shape) Information about objects, such as the number of properties they have and a reference to their prototypes, which V8 maintains internally when objects are created and updated. This allows V8 to efficiently represent objects with the same properties.
(sliced string) When creating a substring, such as when using the JavaScript substring method, V8 might choose to create a sliced string object rather than copying all of the relevant characters from the original string. This new object contains a pointer to the original string and describes which range of characters from the original string to use.
system / Context Local variables from a JavaScript scope which can be accessed by some nested function. Every function instance contains an internal pointer to the context in which it executes, so that it can access those variables.
(system) Various internal objects that haven't yet been categorized in any more meaningful way.

Comparison view

To find leaked objects, compare multiple snapshots to each other. In a web application, usually, doing an action and then the reverse action shouldn't lead to more objects in memory. For example, when opening a document and then closing it, the number of objects in memory should be the same as before opening the document.

To verify that certain operations don't create leaks:

  1. Take a heap snapshot before performing an operation.

  2. Perform the operation. That is, interact with the page in some way that might be causing a leak.

  3. Perform the reverse operation. That is, do the opposite interaction and repeat it a few times.

  4. Take a second heap snapshot.

  5. In the second heap snapshot, change the view to Comparison, comparing it to Snapshot 1.

In the Comparison view, the difference between two snapshots is displayed:

Comparison view

When expanding a constructor in the list, added and deleted object instances are shown.

Containment view

The Containment view allows you to peek inside function closures, to observe virtual machine (VM) internal objects that make up your JavaScript objects, and to understand how much memory your application uses at a very low level:

Containment view

The Containment view shows the following types of objects:

Containment view entry points Description
DOMWindow objects Global objects for JavaScript code.
GC roots The GC roots used by the garbage collector of the JavaScript virtual machine. GC roots are comprised of built-in object maps, symbol tables, VM thread stacks, compilation caches, handle scopes, and global handles.
Native objects Objects created by the browser such as DOM nodes and CSS rules, which are shown in the JavaScript virtual machine to allow automation.

The Retainers section

The Retainers section is displayed at the bottom of the Memory tool and shows all the objects which point to the selected object. The Retainers section is updated when you select a different object in the Summary, Containment, or Comparison view.

In the following screenshot, a string object was selected in the Summary view, and the Retainers section shows that the string is retained by the x property of an instance of the Item class, found in the example-03.js file:

The Retainers section

Hide cycles

In the Retainers section, when you analyze the objects which retain the selected object, you might encounter cycles. Cycles occur when the same object appears more than once in the retainer path of the selected object. In the Retainers section, a cycled object is indicated by being grayed out.

To help simplify the retainer path, hide cycles in the Retainers section by clicking the Filter edges dropdown menu and then selecting Hide cycled:

The Filter edges dropdown menu in the Retainers section, 'Hide cycled' is selected

Hide internal nodes

Internal nodes are objects that are specific to V8 (the JavaScript engine in Microsoft Edge).

To hide internal nodes from the Retainers section, in the Filter edges dropdown menu, select Hide internal.

Configure the Shallow Size column to include an entire object's size

By default, the Shallow Size column in the Memory tool only includes the size of the object itself. The shallow size is the size of the JavaScript heap that's directly held by an object. The shallow size of an object is usually small, because a JavaScript object often only stores its description of the object, not the values, in the object's directly held memory. Most JavaScript objects store their values in a backing store that's elsewhere in the JavaScript heap, and only expose a small wrapper object on the portion of the JavaScript heap that's directly owned by the object. For example, JavaScript Array instances store the contents of the array in a backing store, which is a separate memory location that's not included in the array's shallow size.

Starting with Microsoft Edge 123, you can configure the Shallow Size column to report the entire size of objects, including the size of the object's backing store.

To include the entire size of objects in the Shallow Size column:

  1. In DevTools, click the Customize and control DevTools (Customize and control DevTools icon) button, and then click Settings (Settings icon). Or, while DevTools has focus, press F1.

  2. In the Experiments section, select the checkbox In heap snapshots, treat backing store size as part of the containing object.

  3. Click the Close (x) button of the Settings page, and then click the Reload DevTools button.

  4. Take a new heap snapshot. The Shallow Size column now includes the entire size of objects:

    The Shallow Size column of a heap snapshot

Filter heap snapshots by node types

Use filters to focus on specific parts of a heap snapshot. When looking at all the objects in a heap snapshot in the Memory tool, it can be difficult to focus on specific objects or retaining paths.

To focus only on specific types of nodes, use the Node Types filter, in the upper right. For example, to see only the arrays and string objects in the heap snapshot:

  1. To open the Node Types filter, click Default in the upper right.

  2. Select the Array and String entries.

    The heap snapshot is updated to show only the array and string objects:

    Node Types in a heap snapshot in the Memory tool

Find a specific object

To find an object in the collected heap, you can search using Ctrl+F and give the object ID.

Uncover DOM leaks

The Memory tool has the ability to show the bidirectional dependencies that sometimes exist between browser native objects (DOM nodes, CSS rules) and JavaScript objects. This helps to discover memory leaks that happen because of forgotten detached DOM nodes that remain in memory.

Consider the following DOM tree:

DOM subtrees

The following code sample creates the JavaScript variables treeRef and leafRef, which reference two of the DOM nodes in the tree:

// Get a reference to the #tree element.
const treeRef = document.querySelector("#tree");

// Get a reference to the #leaf element,
// which is a descendant of the #tree element.
const leafRef = document.querySelector("#leaf");

In the following code sample, the <div id="tree"> element is removed from the DOM tree:

// Remove the #tree element from the DOM.
document.body.removeChild(treeRef);

The <div id="tree"> element can't be garbage-collected because the JavaScript variable treeRef still exists. The treeRef variable directly references the <div id="tree"> element. In the following code sample, the treeRef variable is nullified:

// Remove the treeRef variable.
treeRef = null;

The <div id="tree"> element still can't be garbage-collected because the JavaScript variable leafRef still exists. The leafRef.parentNode property references the <div id="tree"> element. In the following code sample, the leafRef variable is nullified:

// Remove the leafRef variable.
leafRef = null;

At this point, the <div id="tree"> element can be garbage-collected. Both treeRef and leafRef must first be nullified, for the whole DOM tree under the <div id="tree"> element to be garbage-collected.

Demo webpage: Example 6: Leaking DOM nodes

To understand where DOM nodes might leak, and how to detect such leakage, open the example webpage Example 6: Leaking DOM nodes in a new window or tab.

Demo webpage: Example 9: DOM leaks bigger than expected

To see why a DOM leak might be bigger than expected, open the example webpage Example 9: DOM leaks bigger than expected in a new window or tab.

Analyze the impact of closures on memory

To analyze the impact of closures on memory, try out this example:

  1. Open the Eval is evil demo webpage in a new window or tab.

  2. Record a heap snapshot.

  3. In the rendered webpage, click the Closures with eval button.

  4. Record a second heap snapshot.

    In the sidebar, the number below the second snapshot should be larger than the number below the first snapshot. This indicates that more memory is being used by the webpage after clicking the Closures with eval button.

  5. In the second heap snapshot, change the view to Comparison, and then compare the second heap snapshot to the first heap snapshot.

    The Comparison view shows that new strings were created in the second heap snapshot:

    The Comparison view, showing that new strings were created in the second snapshot

  6. In the Comparison view, expand the (string) constructor.

  7. Click the first (string) entry.

    The Retainers section is updated and shows that the largeStr variable retains the string selected in the Comparison view.

    The largeStr entry is automatically expanded and shows that the variable is retained by the eC function, which is the closure where the variable is defined:

    The Retainers section, showing that the string is retained by the eC function

Tip: Name functions to differentiate between closures in a snapshot

To easily distinguish between JavaScript closures in a heap snapshot, give your functions names.

The following example uses an unnamed function to return the largeStr variable:

function createLargeClosure() {
    const largeStr = 'x'.repeat(1000000).toLowerCase();

    // This function is unnamed.
    const lC = function() {
        return largeStr;
    };

    return lC;
}

The following example names the function, which makes it easier to distinguish between closures in the heap snapshot:

function createLargeClosure() {
    const largeStr = 'x'.repeat(1000000).toLowerCase();

    // This function is named.
    const lC = function lC() {
        return largeStr;
    };

    return lC;
}

Save and export strings from a heap snapshot to JSON

When taking a heap snapshot in the Memory tool, you can export all string objects from the snapshot to a JSON file. In the Memory tool, in the Constructor section, click the Save all to file button next to the (string) entry:

Save all strings from a heap snapshot to JSON

The Memory tool exports a JSON file that contains all of the string objects from the heap snapshot:

Strings from the heap snapshot, in the JSON file

See also

Note

Portions of this page are modifications based on work created and shared by Google and used according to terms described in the Creative Commons Attribution 4.0 International License. The original page is found here and is authored by Meggin Kearney (Technical Writer).

Creative Commons License This work is licensed under a Creative Commons Attribution 4.0 International License.