Suggest an editImprove this articleRefine the answer for “Profiling memory usage”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Memory is profiled by comparing heap snapshots taken before and after a suspicious scenario, never by a single snapshot.** In Chrome DevTools the Memory tab offers three modes: Heap snapshot to diff two states, Allocation instrumentation on timeline to get allocation stacks with the exact lines of code, and Sampling allocation profiler for a cheap profile with almost no overhead. The Retainers column in a snapshot shows the reference chain that holds an object and keeps the collector away, while the Performance tab with the Memory metric enabled draws the shape of the heap over time. In Node.js the same toolset is available through `node --inspect` and chrome://inspect, plus `heapdump` for a snapshot taken at runtime, `process.memoryUsage()` for the rss and heapUsed counters and `node --trace-gc` for collector pauses. ```javascript setInterval(() => { const m = process.memoryUsage(); console.log(Math.round(m.heapUsed / 1024 / 1024) + 'MB'); }, 5000); ``` **Key point:** a leak is proven not by heap growth itself, but by the heap failing to return to roughly its starting level after a forced GC.Shown above the full answer for quick recall.Answer (EN)Image**Memory profiling is measuring how much heap an application holds, which objects live in it and which references stop the garbage collector from freeing them.** In the browser this is done in the Memory tab of Chrome DevTools, in Node.js through the inspector, heap snapshots and the `process.memoryUsage()` counters. ## Theory ### TL;DR - The basic technique: take a heap snapshot before a scenario, replay the scenario, take a snapshot after and diff them in Comparison mode by Delta Size and Delta Count. - The Retainers column shows the reference chain that holds an object; Distance shows how close the object is to a GC root. - Allocation instrumentation on timeline gives allocation stacks with lines of code, Sampling allocation profiler is cheaper but approximate. - In Node.js you have `node --inspect`, heap snapshots in DevTools, `heapdump`, `process.memoryUsage()` and `node --trace-gc`. - The sign of a leak: after a forced GC the heap does not return to its starting level. - The usual culprits: detached DOM nodes, listeners that are never removed, live timers, subscriptions without unsubscribe, caches with no size limit. ### Quick example ```javascript // Node.js: the cheapest way to see the memory trend setInterval(() => { const m = process.memoryUsage(); const mb = (v) => Math.round(v / 1024 / 1024) + 'MB'; console.log({ rss: mb(m.rss), // the whole resident memory of the process heapUsed: mb(m.heapUsed), // actually occupied inside the JS heap heapTotal: mb(m.heapTotal), // allocated for the heap ext: mb(m.external), // buffers outside the heap }); }, 5000); // If heapUsed climbs monotonically and never comes back down, it is time // to take heap snapshots and find the owner of that growth. ``` ### Profiling in the browser (Chrome DevTools) **1) The basic "is the heap growing" scenario.** 1. Open DevTools, the **Memory** tab. 2. Pick **Heap snapshot** and press **Take snapshot** (snapshot 1). 3. Perform the suspicious actions: SPA navigation, opening and closing modals, lists and so on. 4. Press **Take snapshot** again (snapshot 2). 5. Compare: **Comparison** mode, sorted by **Delta Size** or **Delta Count**. Look for growing owners (classes and constructors), **Detached DOM nodes**, big arrays and `Map` instances. What to look at inside a snapshot: - **Retainers**, the retaining chains: they show which reference exactly stops GC from freeing the object. - **Distance**: a small value means the object sits close to a root, and such objects are rarely released. **2) Real time: where objects are actually created.** The **Allocation instrumentation on timeline** mode: 1. Memory, then **Allocation instrumentation on timeline**, then **Start**. 2. Replay the scenario for 10-30 seconds. 3. **Stop**, click the spikes on the chart and read the **Allocation stacks**, the lines of code where allocations happen. Upside: you see the hot allocation spots right at their place in the code. Downside: a lot of noise, but for catching spikes this mode is the best. **3) The light mode.** **Sampling allocation profiler** is less precise but has almost no overhead. It is handy when the bug is hard to reproduce quickly and the profile has to run for a long time. **4) The memory timeline.** In the **Performance** tab enable the **Memory** metric and record. If after a manual GC (the bin icon) the heap **does not return** to roughly its starting level, there is a leak. **5) Quick checks from the console.** ```javascript // Current heap size (Chromium only): performance.memory.usedJSHeapSize; // Experimental, more detailed API: if (performance.measureUserAgentSpecificMemory) { performance.measureUserAgentSpecificMemory().then(console.log); } ``` ### Profiling in Node.js **1) Inspection through DevTools.** ```javascript node --inspect index.js # or stopping on the first line node --inspect-brk index.js ``` Then open chrome://inspect in Chrome, press **Open dedicated DevTools** and work in the **Memory** tab exactly as in the browser. **2) A heap snapshot from runtime.** ```javascript npm i heapdump ``` ```javascript import heapdump from 'heapdump'; setInterval(() => { heapdump.writeSnapshot(`./heap-${Date.now()}.heapsnapshot`); }, 60_000); ``` Open the `.heapsnapshot` files in DevTools and compare them with each other. **3) Quick counters.** `process.memoryUsage()` every few seconds, as in the example above. If `heapUsed` saws upwards and never comes back, suspect a leak. **4) GC traces for diagnostics.** ```javascript node --trace-gc index.js ``` Watch the frequency and the duration of the pauses. A steadily rising "after GC" level is a bad sign: the collector runs, but has nothing it is allowed to free. | Tool | What it gives | Overhead | | --- | --- | --- | | Heap snapshot | the full object graph, Retainers, snapshot diffing | high | | Allocation instrumentation on timeline | allocation stacks with lines of code | high | | Sampling allocation profiler | approximate hot allocation spots | low | | Performance, Memory metric | the shape of the heap over time, the effect of a manual GC | medium | | `process.memoryUsage()` | rss, heapUsed, heapTotal, external | close to zero | | `node --trace-gc` | GC pause frequency and duration, the level after GC | low | ### SPA and React: the verification protocol 1. Open page A, take **Heap snapshot 1**. 2. Go to page B and back to A (or open and close the component), take **snapshot 2**. 3. Press **GC** (the bin icon), take **snapshot 3**. 4. Compare snapshots 1 and 3. If the objects of page A or of the component are **still there** and their count or size grows after repeats, that is a leak. What to look for: - **Detached DOM nodes**; - `EventListener` handlers without `removeEventListener`; - live `setTimeout` and `setInterval` timers; - zombie subscriptions (WebSocket, RxJS); - `Map` or `Set` caches with no TTL and no eviction; - large structures in Context or Redux that nobody ever resets. React specifics: - check every `useEffect` for a cleanup, that is, for a returned function: ```javascript useEffect(() => { const id = setInterval(fetchData, 5000); return () => clearInterval(id); // mandatory! }, []); ``` - React DevTools **Profiler**: the component must receive an **Unmount** when you leave the page; - watch DOM `ref` values: after unmount they must become `null`. ### The leak hunting method 1. **Fix the symptom in numbers**: "in N minutes the heap grows from X to Y". 2. **Narrow the scenario** down to the minimal reproducible sequence of actions. 3. **Collect data**: heap snapshots before and after, an Allocation timeline, a Performance recording with the Memory metric. 4. **Find the owner**: the class or constructor that grows, then read its **Retainers**. 5. **Tie it to the code**: file and line through Allocation stacks or source maps. 6. **Fix it**: add cleanup, drop the stray references, bound the cache. 7. **Verify**: repeat steps 1-3, the growth must be gone. 8. **Automate** where possible: an e2e script that measures `usedJSHeapSize` step by step. ### Common hints in profiler reports - **Detached HTMLDivElement** is growing: a listener was not removed, or a reference to removed DOM is still held somewhere. - Arrays and objects inside a `Map` or `Set` grow: the cache has no TTL and no LRU eviction. - Many `Timeout` and `Interval` entries: `clearTimeout` and `clearInterval` are missing. - Plenty of component instances after navigation: the component does not unmount, or closures and subscriptions keep it alive. - Growth inside a third party library: wrap it in your own adapter with cleanup, or replace it. ### Common mistakes - Taking one snapshot and drawing conclusions from it. A leak is visible only when two or three snapshots are compared. - Not pressing GC before the control snapshot: some objects are still alive simply because the collector has not run yet. - Treating any growth of `heapUsed` as a leak. V8 does not collect until it has to, so what matters is the level **after** GC, not the peak before it. - Profiling a dev build: source maps, HMR and DevTools themselves retain objects and produce false detached nodes. - Relying on `performance.memory`: it is a non standard Chromium only API whose values are coarsened for security reasons. - Profiling in a tab with extensions enabled: they add their own allocations and detached nodes to your report. - Hunting a leak without a minimal scenario: the noise from unrelated actions will be louder than the leak itself. - Removing the symptom and never verifying the fix with a second profiling run.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.