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

  1. Open Chrome DevTools: F12 (Win/Linux) or Cmd + Option + I (macOS).
  2. Navigate to the Memory panel.
  3. Select Heap snapshot from the profiling type dropdown.
  4. Clear console logs (Ctrl/Cmd + L) and disable third-party extensions to eliminate instrumentation noise.
  5. Click Take snapshot. Label it Snapshot 1 (Baseline).
  6. 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 Size value for regression tracking.

2. Trigger the Target Operation

  1. Execute the exact user flow suspected of leaking memory (e.g., open/close modal, route navigation, repeated XHR dispatch).
  2. Ensure the operation completes fully. If testing event listeners or detached DOM nodes, verify the UI has visually returned to its initial state.
  3. Wait 2–3 seconds. Allow microtask queues, requestAnimationFrame cycles, 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

  1. Return to the Memory tab.
  2. Click the Collect garbage (trash can) icon in the Console or Memory panel. Repeat 2–3 times to sweep transient allocations.
  3. Click Take snapshot again. Label it Snapshot 2 (Post-Action).
  4. 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

  1. Select Snapshot 2 from the dropdown.
  2. Change the view mode from Summary to Comparison.
  3. DevTools automatically computes the delta against Snapshot 1.
  4. 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.
  1. Sort by # Delta descending. Persistent leaks show positive # Delta values that do not drop to zero after repeated GC cycles.

5. Filtering & Retainer Chain Analysis

  1. Use the class filter to narrow results: (array), (string), HTMLDivElement, or custom constructor names (e.g., MyComponent).
  2. Expand the @ symbol or click the object name to reveal the Retainers tree.
  3. Trace the shortest path to the GC root (e.g., Window, Global, DOMWindow). This chain identifies the exact reference preventing collection.
  4. 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 2 comparison will highlight Uint8Array and Function instances.
  • Size Delta+5.2 MB (5 × 1MB buffers + closure overhead).
  • Retainer chain points directly to leakArray (attached to Window), 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:

  • # Delta0 across identical operations
  • Size Delta < 50 KB (accounting for unavoidable V8 overhead)
  • GC pause duration drops from >50ms to <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.