This tutorial explains how to use the Heap Profiler for uncovering memory leaks in your applications.
Read the Memory 101 page to become familiar with the terms used in this document.
Note: If you are a Web Developer and want to get the latest version of Developer Tools, you should use the Google Chrome release from the Developer Channel.
- Basic functions
- Views in detail
Taking a snapshot
On the Profiles tab, select the Take Heap Snapshot option and press Start:
Snapshots can be removed (both from DevTools and renderer's memory) by pressing the Clear all profiles button:
Note: Closing the DevTools window will not delete collected profiles from the renderer's memory. When reopening DevTools, all previously taken snapshots will reappear in the list of snapshots.
Switching between snapshot views
A snapshot can be viewed from different perspectives for different tasks. To switch between views, use the selector at the bottom of the view:
There are four views:
- Summary — shows objects grouped by the constructor name;
- Comparison — displays difference between two snapshots;
- Containment — allows exploration of heap contents;
- Dominators — shows dominators tree.
Looking up color coding
Properties and property values of objects have different types and are colored accordingly. To view the type color legend, press the question mark button on the status bar:
Each property has one of four types::
- property — a regular property with a name,
accessed via the
.(dot) operator, or via
(brackets) notation, e.g.
- element — a regular property with a numeric
index, accessed via
- context variable — a variable in a function context, accessible by its name from inside a function closure;
Views in detail
Initially, a snapshot opens in the Summary view, displaying object totals, which can be expanded to show instances:
Top-level entries are "total" lines. They display the constructor name, and group all objects created using this constructor. The number of object instances is displayed in the # column. The Shallow size column displays the sum of shallow sizes of all objects created by a certain constructor function. The Retained Size column displays the maximum retained size among the same set of objects.
After expanding a total line in the upper view, all of its instances are displayed. For each instance, its shallow and retained sizes are displayed in the corresponding columns. The number after the @ character is the object unique ID, allowing to compare heap snapshots on per-object basis.
Try this demo page (opens in a new tab) to understand how the Summary view can be used.
When more than one heap snapshot is taken, it is possible to compare them, and find leaked objects. To verify that a certain application operation doesn't create leaks (e.g. usually a pair of direct and reverse operations, like opening a document, and then closing it, should not leave any garbage), you may follow the scenario below:
- take a heap snapshot before performing an operation;
- perform an operation (e.g. open a document);
- perform a reverse operation (close the document);
- take a heap snapshot.
In the Comparison view, the difference between two snapshots is displayed. When expanding a total entry, added and deleted object instances are shown:
Try this demo page (opens in a new tab) to get an idea how to use snapshot comparison for detecting leaks.
The view provides several entry points:
- GC roots — actual GC roots used by VM's garbage collector;
Below is the example of what the Containment view looks like:
Try this demo page (opens in a new tab) for finding out how to explore closures and event handlers using the view.
Uncovering DOM leaks
Native objects are most easily accessible from Summary and Containment views — there are dedicated entry nodes for them:
Try this demo page (opens in a new tab) to play with detached DOM trees.
The Dominators view shows the dominators tree for the heap graph. The Dominators view looks similar to the Containment view, but lacks property names. This is because a dominator of an object may lack direct references to it, that is, the dominators tree is not a spanning tree of the graph. But this only serves for good, as helps us to identify memory accumulation points quickly.
Try this demo page (opens in a new tab) to train yourself in finding accumulation points.
The retaining paths view is always displayed. To activate it, click on an
object in the upper pane, regardless of what view is currently active. The
profiler will start looking up for simple paths from roots to the object
selected. It will start from shorter paths, gradually increasing walking
distance. It is possible to restrict the roots set to
objects, instead of all GC roots. This is helpful for finding references
originating from the application itself — they are the most probable
sources for leaks:
DOMWindow@1235.listeners.handler["on click event"]
Such paths can be easily evaluated in the debugger console if the appropriate context has been entered. For example, if an application has several iframes, execution must be suspended during running a function code belonging to it). Evaluating a path makes it possible to examine the complete contents of each object, including properties whose values not stored in the heap, and thus not captured in a snapshot.
Often it happens that an object is retained via closures. Closure references
are displayed using the
Even more advanced are paths including "hidden"
objects. Normally, the existence of such paths doesn't mean that an
application has a leak. Some of the paths are "low level"
properties internal arrays,
thus a property value can be displayed as held directly by an object
actually accesses it.) Knowing these details might help in getting an
understanding of how the virtual machine works. However, for most of web
application developers it is safe to ignore them. Below are some examples of
- I don't see all the properties of objects, I don't see non-string values for them! Why?
- What does the number after
@char mean — is this an address or an ID? Is the ID value really unique?
- This is an object ID. Displaying an object's address makes no sense, as objects are moved during garbage collections. Those object IDs are real IDs — that means, they persist among multiple snapshots taken. This allows precise comparison between heap states. Maintaining those IDs adds an overhead to GC cycles, but it is only initiated after the first heap snapshot was taken — no overhead if heap profiles aren't used.
- Are "dead" (unreachable) objects included in snapshots?
- No. Only reachable objects are included in snapshots. Also, taking a snapshot always starts with doing a GC.
- What comprises GC roots?
- Many things:
- built-in object maps;
- symbol table;
- stacks of VM threads;
- compilation cache;
- handle scopes;
- global handles.