Why Chrome Task Manager and DevTools Report Different Memory
Chrome’s Task Manager says your tab uses 900 MB while a heap snapshot in DevTools totals 120 MB, and you need to know which number is real — this guide sits under Mastering the Chrome DevTools Memory Tab within Browser DevTools & Performance Profiling Workflows, and explains what each tool actually counts.
| Symptom | Root Cause | Immediate Action | Measurable Impact |
|---|---|---|---|
| Task Manager “Memory footprint” is 5–10× the heap snapshot total | Footprint counts the whole renderer process: DOM, images, GPU buffers, code, fonts | Enable the JavaScript memory column and compare to it instead | Removes 70–90% of the apparent discrepancy |
| JavaScript memory column shows “400 MB (150 MB live)” | First figure is reserved heap, parenthesised figure is used heap | Track the live figure over time, not the reserved one | Stops chasing space V8 has reserved but not filled |
| Heap snapshot is smaller than the Performance panel’s JS heap line | Taking a snapshot forces a full GC and records only reachable objects | Compare snapshots with snapshots, and timelines with timelines | Consistent baselines between runs |
| Two tabs of the same site show identical, huge memory | Both tabs share one renderer process under site isolation | Check the process ID column; profile one tab at a time | Correct attribution of memory to a single page |
| Memory drops sharply when you open DevTools | Opening DevTools and snapshotting triggers GC | Read Task Manager before attaching DevTools | Avoids an observer effect hiding real growth |
Root Cause: Four Different Questions, Four Different Numbers
Chrome’s memory tools each answer a different question, and the numbers only look contradictory because they are measuring different boundaries. The widest boundary is the Memory footprint column in Chrome Task Manager (⋮ → More tools → Task manager, or Shift+Esc on Windows and Linux). It reports the private memory of the renderer process: the V8 JavaScript heap, but also Blink’s DOM and layout objects, decoded images, style data, compiled code, font caches, network buffers, and memory allocated by the GPU path on behalf of the page. A single high-resolution image gallery can add hundreds of megabytes of decoded bitmaps that never appear in a JavaScript heap snapshot at all.
The next boundary is the JavaScript memory column, which is hidden by default — right-click the Task Manager column header to enable it. It shows two figures such as 412,000K (156,000K live). The first is how much memory V8 has reserved for this renderer’s JavaScript heaps; the second, in parentheses, is how much of that reservation is occupied by live objects. V8 grows its reservation in chunks and returns it lazily, so the first figure lags behind real usage in both directions. The parenthesised figure is the one that moves with your code.
The narrowest boundary is the heap snapshot. Before serialising the heap, DevTools asks V8 to run a full garbage collection, so everything unreachable is gone. What remains is only the reachable object graph for the selected JavaScript context. That is why a snapshot total is almost always smaller than the live figure you saw a second earlier: garbage that was still sitting in the heap has just been collected. The Performance panel’s memory track sits in between — it samples the used JS heap during recording without forcing collection, so it shows the sawtooth of allocation and garbage collection rather than the post-GC floor.
Process sharing adds one more twist. With site isolation, Chrome usually groups tabs from the same site into one renderer process, so two tabs of your app can report the same footprint because they are the same process. The Task Manager’s Process ID column makes this visible, and it is the first thing to check before assuming one page is responsible for the whole figure.
Step-by-Step Fix
- Read Task Manager before opening DevTools. Open ⋮ → More tools → Task manager, right-click the header and enable JavaScript memory and Process ID. Note the footprint and the live JS figure for your tab. Verification: you have two numbers, for example
Memory footprint 910 MBandJavaScript memory 412,000K (156,000K live). - Check whether the process is shared. Look for other tabs or iframes with the same Process ID. If several rows share it, close the others or open your page in a fresh profile. Verification: your tab is the only row with that process ID.
- Split the footprint into JS and non-JS. Subtract the live JS figure from the footprint. If the remainder is large (hundreds of MB), the growth lives in the DOM, images or GPU memory rather than in JavaScript objects. Verification: you can say which side of the split is growing when you repeat the user flow.
- Capture the post-GC floor. Open DevTools → Memory → Heap snapshot and take one snapshot. Its total in the sidebar is the reachable JavaScript floor. Verification: the snapshot total is at or below the live JS figure from step 1; a large gap means a lot of collectable garbage was pending.
- Record the trend, not the level. Open DevTools → Performance, tick Memory, and record the flow three times. Watch whether the JS heap line’s troughs rise between repetitions. Verification: flat troughs mean no JS retention even if the footprint is high; rising troughs mean a leak worth snapshot-diffing.
- Pick one number per question and log it. Use the live JS figure (or
performance.measureUserAgentSpecificMemory()in the field) for JavaScript leaks, and the footprint for “will this tab be killed on a low-memory device”. Verification: your bug report states which metric regressed and by how many MB.
Command and Code Reference
Use case: log the same numbers from inside the page so you can correlate them with user actions. performance.memory is Chromium-only and coarse, but it mirrors the reserved and used JS heap figures; measureUserAgentSpecificMemory() is the modern, more complete replacement when the page is cross-origin isolated.
// memory-probe.js — paste into the DevTools Console or ship behind a debug flag
async function memoryProbe(label) {
const mb = (n) => (n / 1048576).toFixed(1) + ' MB';
const out = { label };
// Legacy, Chromium-only: used vs reserved JS heap for this context
if (performance.memory) {
out.jsUsed = mb(performance.memory.usedJSHeapSize); // ~ the "live" figure
out.jsReserved = mb(performance.memory.totalJSHeapSize); // ~ the reserved figure
}
// Modern API: includes DOM and iframes, requires crossOriginIsolated
if (self.crossOriginIsolated && performance.measureUserAgentSpecificMemory) {
const result = await performance.measureUserAgentSpecificMemory();
out.uaTotal = mb(result.bytes); // may take seconds: it waits for a GC
}
console.table(out);
}
memoryProbe('after opening report');
Use case: capture the renderer footprint from outside the page in an automated run. The Chrome DevTools Protocol exposes the same process-level counters the Task Manager reads.
// footprint.mjs — node footprint.mjs (needs puppeteer)
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('http://localhost:5173/', { waitUntil: 'networkidle0' });
// JSHeapUsedSize ≈ live JS; Nodes and Documents expose DOM growth
const m = await page.metrics();
console.log({
jsUsedMB: (m.JSHeapUsedSize / 1048576).toFixed(1),
jsTotalMB: (m.JSHeapTotalSize / 1048576).toFixed(1),
domNodes: m.Nodes, // non-JS memory driver #1
documents: m.Documents, // leaked iframes show up here
});
await browser.close();
Verification and Regression Prevention
You have resolved the confusion when every memory figure in your bug report is labelled with its source and boundary: “live JS heap (Task Manager) rose from 150 MB to 310 MB over five flows” is actionable; “Chrome uses 900 MB” is not. For JavaScript retention, the target is flat heap troughs across repetitions and a snapshot comparison that shows no constructor growing by more than a few hundred KB per flow. For whole-process growth, the target is a footprint that returns to within 10% of its starting level after the flow is closed.
Guard against regressions by tracking both boundaries in automation. Run the Puppeteer metrics script after each repetition of a scripted flow and fail CI when JSHeapUsedSize grows more than 10 MB across five repetitions, or when Nodes keeps rising — DOM growth is the most common cause of a footprint that JavaScript numbers do not explain, and it links directly to detached DOM node retention. For the process-level view in real users’ browsers, see how the Memory tab compares with the Performance memory lane.
Frequently Asked Questions
Which number should I report in a memory bug?
Report the live JavaScript heap trend if you suspect a JavaScript leak, and the Task Manager memory footprint if the complaint is that the tab crashes or slows the device. Always include how the number changed across repetitions rather than a single reading, because both figures fluctuate with garbage collection timing.
Why does memory go down when I open DevTools?
Opening DevTools, and especially taking a heap snapshot, forces garbage collection. Garbage that was waiting to be collected disappears, so the numbers drop. Read the Task Manager figures before attaching DevTools if you want the undisturbed baseline.
Does the heap snapshot include DOM nodes?
It includes the JavaScript wrappers for DOM nodes and entries for native DOM objects that JavaScript can reach, which is why detached nodes appear in snapshots. It does not include decoded image pixels, GPU textures, or most rendering structures, which only show up in the Task Manager footprint.
Related
- Mastering the Chrome DevTools Memory Tab — the parent topic on the Memory panel’s profiling types
- Chrome Memory Tab vs Performance Memory Lane — when to snapshot and when to record a timeline
- Retained Size vs Shallow Size Explained — reading sizes inside the snapshot itself
- Browser DevTools & Performance Profiling Workflows — the section overview