Record heap snapshots using the Memory tool ("Heap snapshot" profiling type)
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
Open the webpage you want to analyze. For example, open the Scattered objects demo page in a new window or tab.
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.
In DevTools, on the Activity Bar, select the Memory tab. If that tab isn't visible, click the More tools () button.
In the Select profiling type section, select the Heap snapshot option button.
Under Select JavaScript VM instance, select the JavaScript VM that you want to profile.
Click the Take snapshot button:
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:
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:
Clear snapshots
To clear all snapshots from the Memory tool, click the Clear all profiles () icon:
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:
Items omitted from heap snapshots
Properties that are implemented using getters that run native code aren't captured in the heap snapshot, because such properties are not stored on the JavaScript heap.
Non-string values, such as numbers, aren't captured.
Summary view
The Summary view in the Memory tool lists:
- Object constructor groups.
- Special category names, such as (array), (compiled code), or a list of properties such as {foo, bar, baz}.
Initially, a heap snapshot opens in the Summary view, which displays a list of constructors:
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
The Summary view in the Memory tool lists object constructor groups:
The constructor groups in the Summary view might be built-in functions such as Array
or Object
, or they might be functions that are 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
The Summary view in the Memory tool includes the following special category names, which aren't based on constructors. Most of these category names are displayed in parentheses.
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. |
(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) | Various internal objects that haven't yet been categorized in any more meaningful way. |
{foo, bar, baz} | Plain JavaScript objects categorized by interface (property list), in curly braces. Plain JavaScript objects are not listed in a category named Object, but are instead represented by names and categories that are based on the properties that the object contains, such as {foo, bar, baz}. |
InternalNode | Objects allocated outside of V8, such as C++ objects defined by Blink, Microsoft Edge's rendering engine. |
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. |
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:
Take a heap snapshot before performing an operation.
Perform the operation. That is, interact with the page in some way that might be causing a leak.
Perform the reverse operation. That is, do the opposite interaction and repeat it a few times.
Take a second heap snapshot.
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:
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:
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:
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:
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.
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:
To open the Node Types filter, click Default in the upper right.
Select the Array and String entries.
The heap snapshot is updated to show only the array and string objects:
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.
For detached elements, see also Find DOM tree memory leaks ("Heap snapshot" profiling type > Detached), below.
Consider the following DOM tree:
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:
Open the Eval is evil demo webpage in a new window or tab.
Record a heap snapshot.
In the rendered webpage, click the Closures with eval button.
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.
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:
In the Comparison view, expand the (string) constructor.
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 theeC
function, which is the closure where the variable is defined:
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:
The Memory tool exports a JSON file that contains all of the string objects from the heap snapshot:
Find DOM tree memory leaks ("Heap snapshot" profiling type > Detached)
One way to find and display all of the detached elements on a webpage is to use the Memory tool's Heap snapshot profiling type, then type Detached in the Filter by class text box, as follows. See also Tools for investigating detached elements in Fix memory problems.
The following code produces detached DOM nodes:
var detachedTree;
function create() {
var ul = document.createElement('ul');
for (var i = 0; i < 10; i++) {
var li = document.createElement('li');
ul.appendChild(li);
}
detachedTree = ul;
}
document.getElementById('create').addEventListener('click', create);
This code creates a ul
node with ten li
children. The nodes are referenced by the code, but they don't exist in the DOM tree, so each node is detached.
Heap snapshots are one way to identify detached nodes. A heap snapshot shows how memory is distributed among the JS objects and DOM nodes for your page at the point of time of the snapshot.
Use the "Heap snapshot" profiling type to find detached elements
To use the Heap snapshot profiling type to find detached elements:
Open a webpage, such as the Detached Elements demo webpage, in a new window or tab.
Right-click the webpage, and then select Inspect. Or, press Ctrl+Shift+I (Windows, Linux) or Command+Option+I (macOS).
DevTools opens.
In DevTools, in the Activity Bar, select the Memory () tool.
If that tab isn't visible, click the More Tools () button, and then select Memory. The Memory tool opens:
If the Heap snapshot option button isn't shown, because a profile is already displayed, in the upper left, click Profiles ().
You don't need to select the Heap snapshot option button at this point, because the webpage hasn't generated any detached elements yet.
Generate messages, which will be stored by the JavaScript instance of the Room class:
In the demo webpage, click the Fast traffic button.
The demo webpage begins generating messages and displaying them in the webpage:
After some messages are displayed, click the Stop button in the demo webpage.
Each message is a
<div class="message">
element that's referenced by the Room 1 instance of theRoom
class. There are no detached elements in the webpage DOM tree, because all of the message elements are attached to the present, Room 1 instance of the Room class.Change to a different instance of the Room class, so elements become detached:
In the demo webpage, click the Room 2 button, which corresponds to another instance of the
Room
class.In the webpage, the messages disappear:
The messages that were generated for the Room 1 instance of the Room class (
<div class="message">
elements) are no longer attached to the DOM, but they're still referenced by the Room 1 instance of the Room class. They are detached elements, which can cause memory leaks, unless they are going to be used again by the webpage.Get the list of detached elements:
In DevTools, in the Memory tool, click the Collect garbage () icon:
The browser runs garbage collection, removing any nodes that are no longer referenced by a JavaScript object.
In the Memory tool, select the Heap snapshot option button.
Click the Take snapshot button at the bottom of the Memory tool.
The snapshot is processed, loaded, and then listed in the Profiles sidebar, in the Heap snapshots section.
In the Filter by class text box, type detached:
The detached DOM elements that can't be garbage-collected are displayed.
Identify the JavaScript code that references a particular detached element:
In the heap snapshot, expand a Detached object, such as Detached <div>, and then select a Detached <div class="message"> node.
Information is displayed in the Retainers pane at the bottom of the Memory tool.
In the Retainers pane, click the
room.js:13
link for an unmounted in Room item under Array. The Sources tool opens, displayingroom.js
, scrolled to line 13:To inspect the possible memory leak, study the code that uses the
unmounted
array and make sure that the reference to the node is removed when it is no longer needed.To return to the Memory tool, in the Address Bar, select the Memory tool.
For additional ways to see detached elements, see Tools for investigating detached elements in Fix memory problems.
See also
- Tools for investigating detached elements in Fix memory problems.
- Memory terminology
External:
- Finding and debugging memory leaks in JavaScript with Chrome DevTools - slide deck (by Gonzalo Ruiz de Villa), which also applies to Microsoft Edge DevTools.
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).
This work is licensed under a Creative Commons Attribution 4.0 International License.