How to take and compare heap snapshots in Chrome step by step
Progressive UI lag, tab crashes, and sustained GC pauses >50ms are primary symptoms of unbounded object retention or closure leaks. The fastest path to resolution is deterministic heap diffing. This guide provides the exact DevTools workflow to capture V8 heap states, isolate delta allocations, and trace retention chains. For deeper context on retention trees and object classification, consult Interpreting Heap Snapshots for Memory Analysis.
1. Environment Preparation & Baseline Capture
- Open Chrome DevTools:
F12(Win/Linux) orCmd + Option + I(macOS). - Navigate to the Memory panel.
- Select Heap snapshot from the profiling type dropdown.
- Clear console logs (
Ctrl/Cmd + L) and disable third-party extensions to eliminate instrumentation noise. - Click Take snapshot. Label it
Snapshot 1 (Baseline). - Metric Check: Wait for the spinner to finish. The baseline should reflect the idle state after initial hydration and at least one automatic GC cycle. Record the
Total Sizevalue for regression tracking.
2. Trigger the Target Operation
- Execute the exact user flow suspected of leaking memory (e.g., open/close modal, route navigation, repeated XHR dispatch).
- Ensure the operation completes fully. If testing event listeners or detached DOM nodes, verify the UI has visually returned to its initial state.
- Wait 2–3 seconds. Allow microtask queues,
requestAnimationFramecycles, and browser render threads to flush before proceeding. Capturing during active layout or network activity injects browser-internal noise.
3. Capture Post-Action Snapshot & Force GC
- Return to the Memory tab.
- Click the Collect garbage (trash can) icon in the Console or Memory panel. Repeat 2–3 times to sweep transient allocations.
- Click Take snapshot again. Label it
Snapshot 2 (Post-Action). - Why this matters: Skipping forced GC before Snapshot 2 introduces false positives from short-lived V8 JIT caches, string interning, and temporary render buffers.
4. Execute the Heap Diff
- Select
Snapshot 2from the dropdown. - Change the view mode from Summary to Comparison.
- DevTools automatically computes the delta against
Snapshot 1. - Focus on these columns:
# Delta: Net change in object instances.Size Delta: Net change in bytes.Retained Size: Total memory freed if this object and its exclusively referenced children are collected.
- Sort by
# Deltadescending. Persistent leaks show positive# Deltavalues that do not drop to zero after repeated GC cycles.
5. Filtering & Retainer Chain Analysis
- Use the class filter to narrow results:
(array),(string),HTMLDivElement, or custom constructor names (e.g.,MyComponent). - Expand the
@symbol or click the object name to reveal the Retainers tree. - Trace the shortest path to the GC root (e.g.,
Window,Global,DOMWindow). This chain identifies the exact reference preventing collection. - Document retention paths and integrate findings into your standardized Browser DevTools & Performance Profiling Workflows for iterative optimization.
Simulated Closure Retention for Testing
Run this snippet in the Console to generate predictable retained objects. Execute attachLeak() 5 times, force GC, then capture Snapshot 2.
const leakArray = [];
function attachLeak() {
const largeBuffer = new Uint8Array(1024 * 1024); // ~1MB
const handler = () => console.log('retained');
document.body.addEventListener('click', handler);
leakArray.push({ buffer: largeBuffer, ref: handler });
}
// Execute: attachLeak(); // Run 5 times
Expected Delta Output:
Snapshot 2comparison will highlightUint8ArrayandFunctioninstances.Size Delta≈+5.2 MB(5 × 1MB buffers + closure overhead).- Retainer chain points directly to
leakArray(attached toWindow), confirming the closure leak.
Validation Checklist & Common Pitfalls
| Pitfall | Symptom | Fix |
|---|---|---|
| Skipping forced GC | # Delta shows thousands of (string) or (array) objects |
Always click the trash can icon 2–3x before Snapshot 2. Transient allocations should drop to # Delta ≈ 0. |
| Shallow vs. Retained Size | Optimizing large objects yields minimal memory recovery | Filter by Retained Size, not Shallow Size. Retained size includes exclusively referenced descendants. |
| Capturing during active state | Heap diff flooded with LayoutObject or V8Script entries |
Wait for network idle and render completion. Use the Performance panel to verify Main Thread is idle before capturing. |
| DOM wrapper blind spots | Memory grows but JS heap looks clean | Look for Detached DOM tree or HTMLDivElement. Trace retainers to orphaned event listeners or global caches. |
| Count-only analysis | High # New counts trigger false alarms |
Verify the retainer chain to the GC root. High counts are normal if # Delta and Size Delta remain stable across iterations. |
Measuring Fix Impact: After applying the patch, repeat the exact workflow. A successful resolution will show:
# Delta≈0across identical operationsSize Delta<50 KB(accounting for unavoidable V8 overhead)- GC pause duration drops from
>50msto<10ms(verify via Performance panel)
Frequently Asked Questions
Why does the heap diff show thousands of new objects after a simple UI interaction? V8 optimizes string concatenation, JIT compilation, and DOM reconciliation by creating short-lived allocations. Force a garbage collection cycle before capturing Snapshot 2. If the delta persists after multiple GC passes, those objects are actively retained by a reference chain.
How do I distinguish between a memory leak and normal cache growth?
Leaks exhibit monotonic growth across repeated identical operations. Normal caches plateau or evict entries under memory pressure. Use the comparison view to verify if # Delta increases linearly with each iteration.
Can I compare heap snapshots across different browser sessions?
Yes, but only if the application state and V8 version are identical. Load both .heapsnapshot files into DevTools via the Load button. Cross-session diffs are effective for tracking memory regression across deployments.
How do I export snapshots for CI or offline review?
Click the Save icon (disk) next to each snapshot. Exported .heapsnapshot files can be version-controlled, diffed offline, or integrated into automated memory budget checks.