Suggest an editImprove this articleRefine the answer for “How to profile memory usage?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)Profiling memory usage means comparing heap snapshots taken before and after suspicious actions (Heap snapshot in Chrome DevTools, heapdump in Node.js) to find objects that never get released. **Key point:** if the count or size of objects keeps growing after repeating an action and doesn't drop after a manual GC, that's a sign of a memory leak.Shown above the full answer for quick recall.Answer (EN)Image## In the browser (Chrome DevTools) ### 1) Basic scenario: "is the heap growing?" 1. Open DevTools -> **Memory**. 2. Choose **Heap snapshot** -> **Take snapshot** (snapshot #1). 3. Perform the suspicious actions (SPA navigation, opening/closing modals, lists, and so on). 4. **Take snapshot** again (snapshot #2). 5. Compare: the **Comparison** tab -> sort by **Δ Size**/**Δ Count**. Look for "growing" owners (classes/constructors), **Detached DOM nodes**, and large arrays/Maps. What to look at: - **Retainers** (retention chains) - show which reference is preventing GC. - **Distance** - a small value means close to "root"; such objects are rarely released. ### 2) Real-time: "where objects are created" **Allocation instrumentation on timeline**: 1. Memory -> **Allocation instrumentation on timeline** -> **Start**. 2. Reproduce the scenario for 10-30 seconds. 3. **Stop** -> click through the graph's peaks -> look at the **Allocation stacks** (the code lines where allocations happen). Pros: shows the hot spots of allocations "in place". Cons: noisy, but great for catching spikes. ### 3) Light mode **Sampling allocation profiler** - less precise, but almost no overhead. Handy when it's hard to reproduce the bug for a long time. ### 4) Memory timeline In the **Performance** tab, enable the **Memory** metric -> make a recording. If after a "manual GC" (the trash-can icon) the heap **doesn't return** to roughly its original level, there's a leak. ### 5) Quick checks from the console ```javascript // Current heap size (Chromium only): performance.memory.usedJSHeapSize // (Experimental) more detail per agent: if (performance.measureUserAgentSpecificMemory) { performance.measureUserAgentSpecificMemory().then(console.log); } ``` ## In Node.js ### 1) Inspecting via DevTools Run with the inspector and open it in Chrome: ```javascript node --inspect index.js # or when starting a debug session node --inspect-brk index.js ``` In Chrome -> chrome://inspect -> **Open dedicated DevTools** -> the **Memory** tab -> heap snapshots, just like in the browser. ### 2) Taking a heap snapshot at runtime ```javascript npm i heapdump ``` ```javascript import heapdump from 'heapdump'; setInterval(() => { heapdump.writeSnapshot(`./heap-${Date.now()}.heapsnapshot`); }, 60_000); ``` Open the .heapsnapshot file in DevTools and compare. ### 3) Quick counters ```javascript setInterval(() => { const m = process.memoryUsage(); console.log({ rss: Math.round(m.rss/1024/1024)+'MB', heapUsed: Math.round(m.heapUsed/1024/1024)+'MB', heapTotal: Math.round(m.heapTotal/1024/1024)+'MB', ext: Math.round(m.external/1024/1024)+'MB' }); }, 5000); ``` If `heapUsed` keeps sawtoothing upward and never comes back down, that's a suspected leak. ### 4) GC traces (diagnostics) ```javascript node --trace-gc index.js ``` Watch the frequency and duration of pauses; a steady rise in "after GC" is a bad sign. ## For SPA/React (and components in general) ### 1) The de-facto standard check protocol 1. Open page A -> take **Heap snapshot #1**. 2. Go to page B, come back to A (or open/close the component) -> **#2**. 3. Click **GC** (the trash-can icon) -> **#3**. 4. Compare #1 and #3. If page A's/the component's objects **remain**, and their **count/size grows** after repeats, that's a leak. Look for: - **Detached DOM nodes**; - `EventListener` handlers without a matching `removeEventListener`; - active `Timeout`/`Interval`; - "zombie" subscriptions (WebSocket, RxJS); - caches (`Map`/`Set`) with no TTL/cleanup; - large structures in Context/Redux that nobody resets. ### 2) React specifics - Check `useEffect` for **cleanup** (a returned function): ```javascript useEffect(() => { const id = setInterval(fetchData, 5000); return () => clearInterval(id); // required! }, []); ``` - React DevTools **Profiler**: the component should **Unmount** when leaving the page. - Watch `ref`s to the DOM: after unmount they should become `null`. ## A method for finding a leak (briefly) 1. **Pin down the symptom**: "after N minutes the heap grows from X to Y". 2. **Narrow the scenario**: the minimal reproducible sequence of actions. 3. **Gather data**: a Heap snapshot before/after, the Allocation timeline, Perf Memory. 4. **Find the owner**: the class/constructor where the growth happens -> check **Retainers**. 5. **Tie it to the code**: the line/file, via Allocation stacks or sourcemaps. 6. **Fix it**: add a cleanup / remove references / limit the cache. 7. **Verify**: repeat steps 1-3 - the growth should disappear. 8. **Automate it** (if possible): an e2e script that measures `usedJSHeapSize` at each step. ## Common "tells" in reports - A **Detached HTMLDivElement** grows -> a listener/reference to the DOM was never removed. - Arrays/objects in a **Map/Set** grow -> there's no TTL/LRU cleanup. - Lots of `Timeout`/`Interval` -> no `clearTimeout`/`clearInterval`. - A pile of component instances **after navigation** -> it doesn't unmount, or closures/subscriptions "hold" it. - A leak in a **third-party** library -> wrap it in your own adapter with cleanup, or replace it.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.