Firefox Memory Tool: Heap Snapshots and Dominator Trees
This article explains how the Memory tool in the Firefox Web Browser captures heap snapshots and utilizes dominator trees to detect JavaScript memory leaks. It provides a technical breakdown of how Firefox's engine records memory allocation states, how the dominator view calculates retained memory sizes, and the exact steps developers can take to trace and eliminate rogue references that prevent garbage collection.
How Firefox Captures Heap Snapshots
A heap snapshot is a static record of all memory currently used by a web page at a specific moment in time. When you click the "Take snapshot" button in the Firefox Memory panel, the browser's JavaScript engine, SpiderMonkey, briefly pauses the execution of the page's scripts to ensure the memory state does not change during the recording process.
SpiderMonkey then performs a complete traversal of the memory graph.
It starts at the Garbage Collection (GC) roots—fundamental objects that
are always reachable, such as the global window object or
the document tree—and follows every pointer and reference to discover
all reachable objects.
During this traversal, Firefox records:
- JavaScript objects, arrays, and functions.
- Primitive values like strings and numbers.
- DOM nodes and their connections to JavaScript wrappers.
- Internal engine structures and compiled scripts.
Instead of saving the actual data inside these objects, Firefox serializes the metadata: the object types, their memory addresses, their immediate memory footprint, and the edges (references) connecting them to other objects. Once complete, this data is passed to the Firefox Developer Tools interface for visualization, and script execution resumes.
Understanding the Dominator Tree
While the default "Aggregate" view in the Memory tool groups objects by their type, it does not easily show why an object is stuck in memory. To find leaks, you must switch the view dropdown to "Dominators," which organizes the heap snapshot into a dominator tree.
In graph theory, Object A "dominates" Object B if every path from the GC root to Object B must pass through Object A. In practical terms, this means Object A is solely responsible for keeping Object B alive in memory. If Object A is deleted and garbage collected, Object B will automatically be garbage collected as well.
The Dominator view relies on two critical metrics to display this relationship:
- Shallow Size: The amount of memory held by the object itself, excluding the objects it references.
- Retained Size: The Shallow Size of the object plus the Shallow Sizes of all the other objects it dominates.
Firefox constructs the dominator tree by analyzing the reference graph captured in the snapshot and calculating these sizes. It then displays the objects sorted from highest to lowest Retained Size. This immediately highlights the biggest memory bottlenecks in the application.
Finding JavaScript Memory Leaks
A JavaScript memory leak occurs when an object is no longer needed by the application, but a forgotten reference still connects it to a GC root, preventing the garbage collector from freeing the space. You can use heap snapshots and the dominator tree to find these leaks using the following workflow:
1. Capture the Snapshots
Open the Firefox Developer Tools, navigate to the Memory tab, and take a baseline heap snapshot. Next, interact with your web application by performing the actions you suspect are causing a leak (such as opening and closing a modal repeatedly). Click the garbage can icon in the Memory tool to force a manual garbage collection, then take a second snapshot.
2. Analyze the Dominators
Select the second snapshot and change the view mode to "Dominators."
Look at the top rows of the table. You are looking for objects with a
massive Retained Size that often have a very small Shallow Size. For
example, a single Array or Object might have a
Shallow Size of just a few bytes, but a Retained Size of several
megabytes because it is holding onto thousands of child elements or
detached DOM nodes.
3. Trace the Retaining Path
When you find a suspicious object in the dominator tree, click the arrow next to it to expand its children. This shows you the exact objects it is dominating.
To figure out why this parent object has not been garbage
collected, click on the object to highlight it, and look at the
"Shortest Paths" panel at the bottom of the Memory tool. This panel
displays the exact chain of variables, closures, or event listeners
leading from the window root down to your leaking
object.
4. Resolve the Leak
Once the retaining path reveals the specific variable or property
holding the reference—such as an unregistered
addEventListener, an uncleared setInterval, or
a global cache array that never purges old data—you can go into your
source code and explicitly nullify the reference or remove the listener.
Taking a new heap snapshot and checking the dominator tree will confirm
that the Retained Size has dropped and the leak is resolved.