跳到主要内容

如何查看 V8 的内存使用情况:process.memoryUsage()v8.getHeapStatistics()、Heap Snapshot 怎么选?

面试速答(30 秒版 TL;DR)

  • 查看 V8 内存,最常用的不是一种手段,而是三层视角:进程级、V8 堆级、对象级
  • process.memoryUsage() 适合先看整体趋势:rssheapTotalheapUsedexternalarrayBuffers
  • node:v8 模块适合看堆细节:v8.getHeapStatistics() 看堆上限与可用空间,v8.getHeapSpaceStatistics()new_spaceold_space 等分代空间。
  • 如果你要回答“到底是谁没被回收”,只看数字不够,要上 Heap Snapshot / DevTools Memory
  • 面试里最容易答错的一句是:rss 不是 V8 堆大小,heapUsed 也不是整个进程占用。

先建立心智模型:你到底想看哪一层

先按三层视角记:进程级V8 堆级对象级

面试里可以直接这样组织答案:

  1. 先用 process.memoryUsage() 看趋势。
  2. 再用 v8 模块看堆和分代。
  3. 真怀疑泄漏,再抓 Heap Snapshot 看引用链。

一、先看整体:process.memoryUsage()

这是最常用的第一步,因为它成本最低,也最适合接监控。

const { memoryUsage } = require('node:process')

setInterval(() => {
const mem = memoryUsage()
console.log({
rssMB: (mem.rss / 1024 / 1024).toFixed(1),
heapTotalMB: (mem.heapTotal / 1024 / 1024).toFixed(1),
heapUsedMB: (mem.heapUsed / 1024 / 1024).toFixed(1),
externalMB: (mem.external / 1024 / 1024).toFixed(1),
arrayBuffersMB: (mem.arrayBuffers / 1024 / 1024).toFixed(1),
})
}, 5000)

这些字段怎么讲

  • rss:进程当前驻留在物理内存中的总量,包含 V8 堆、C++ 层、代码段、栈等。
  • heapTotal:V8 已经向系统申请到的堆容量。
  • heapUsed:当前真正被 JS 对象占用的堆内存。
  • external:V8 堆外、但由 JS 对象间接持有的外部内存,典型是 C++ 绑定资源。
  • arrayBuffersArrayBuffer / SharedArrayBuffer / Node Buffer 相关内存,这部分也会反映到 external

面试加分点

  • 如果 heapUsed 稳定,但 rss 持续涨,问题不一定在 V8 堆,也可能在原生模块、Buffer、碎片化或其他进程级占用。
  • 如果 heapUsed 持续涨且不回落,才更像是 JS 对象没有被释放。

二、看 V8 堆本身:v8.getHeapStatistics()

当面试官继续追问“默认堆上限怎么看”时,就要切到 node:v8

const v8 = require('node:v8')

const stats = v8.getHeapStatistics()

console.log({
totalHeapMB: (stats.total_heap_size / 1024 / 1024).toFixed(1),
usedHeapMB: (stats.used_heap_size / 1024 / 1024).toFixed(1),
availableMB: (stats.total_available_size / 1024 / 1024).toFixed(1),
heapLimitMB: (stats.heap_size_limit / 1024 / 1024).toFixed(1),
nativeContexts: stats.number_of_native_contexts,
detachedContexts: stats.number_of_detached_contexts,
})

重点字段怎么答

  • heap_size_limit:V8 堆的最大上限,默认值由运行时环境决定,也可能被 --max-old-space-size 影响。
  • total_available_size:离触达堆上限还有多少可用空间。
  • number_of_native_contexts:顶层上下文数量,持续增长通常要警惕泄漏。
  • number_of_detached_contexts:被分离但还未回收的上下文,非 0 往往值得排查。

一个本地示例

在当前仓库这台机器上,我用 Node v24.12.0arm64 实测:

require('node:v8').getHeapStatistics().heap_size_limit / 1024 / 1024
// 4288

这个值只能当 当前环境示例,不能背成“V8 永远就是 4288MB”。不同 Node / V8 版本、系统位数、启动参数下都可能不同。


三、看分代空间:v8.getHeapSpaceStatistics()

如果你想把“为什么 Minor GC 很频繁、为什么老生代顶满后更危险”讲清楚,就应该看 heap space。

const v8 = require('node:v8')

const spaces = v8.getHeapSpaceStatistics().map((space) => ({
name: space.space_name,
sizeMB: (space.space_size / 1024 / 1024).toFixed(1),
usedMB: (space.space_used_size / 1024 / 1024).toFixed(1),
availableMB: (space.space_available_size / 1024 / 1024).toFixed(1),
}))

console.table(spaces)

常见会看到这些空间:

  • new_space:新生代,短命对象多,Minor GC 高频发生。
  • old_space:老生代,长寿对象多,回收更重。
  • code_space:JIT 代码相关空间。
  • map_space:隐藏类等元信息空间。
  • large_object_space:大对象空间。

面试口径

  • 看整体趋势process.memoryUsage()
  • 看堆上限和上下文getHeapStatistics()
  • 看新生代/老生代是否失衡getHeapSpaceStatistics()

四、想知道“谁没被回收”:Heap Snapshot 才是终局工具

很多人会说“我看 heapUsed 一直涨,所以是闭包泄漏”。这种判断太粗。

更稳的排查流程是:

  1. 启动 Node Inspector:
node --inspect app.js
  1. 打开 Chrome DevTools 连接到 Node 进程。
  2. Memory 面板抓 Heap Snapshot。
  3. 对比操作前后的快照,重点看:
    • 哪类对象数量在涨
    • Retainers 是谁
    • 是否存在 Detached Context / 长链路缓存

如果希望在接近内存上限时自动留证,还可以使用:

node --max-old-space-size=512 --heapsnapshot-near-heap-limit=2 app.js

这个思路很适合回答“线上快 OOM 了怎么排查”。


五、线上监控怎么做才靠谱

别只打一次日志,要看趋势。

一个实用思路:

  • 每隔 10 秒采集一次 rssheapUsedheapTotalexternal
  • 记录请求量、QPS、队列堆积、缓存命中率
  • 观察 GC 后基线是否回落

判断标准:

  • 正常抖动:峰值会上去,空闲后会回落
  • 可疑泄漏:重复相同流量模型,基线持续上升

典型题 & 标准答法

Q1:process.memoryUsage() 能直接看出 V8 堆上限吗?

不能。它更适合看进程内存的当前占用。要看堆上限,更稳的是 v8.getHeapStatistics().heap_size_limit

Q2:为什么 rss 很大,但 heapUsed 并不高?

因为 rss 是整个进程驻留内存,不只包含 V8 堆,还包含原生模块、Buffer、代码段、栈等;所以 rss 大不等于 JS 堆泄漏。

Q3:想定位具体泄漏对象,用哪个 API 最合适?

不是某个简单 API,而是 Heap Snapshot。前面的数字类 API 更像“报警器”,Heap Snapshot 才像“现场勘验工具”。


常见追问

  • externalarrayBuffers 为什么经常一起看?
  • 为什么 heapTotal 会大于 heapUsed
  • 为什么看起来“对象删掉了”,rss 还是没有立刻下降?

易错点 / 坑

  • rss 当成 V8 堆大小。
  • heapUsed 的一次上涨直接等同于内存泄漏。
  • 只看一瞬间数据,不看长期趋势。
  • 不区分“看占用”和“找引用链”这两类工具。

速记要点(可背诵)

  • process.memoryUsage() 看进程视角,v8.getHeapStatistics() 看堆视角,Heap Snapshot 看对象视角。
  • rss 是总占用,heapUsed 才是已使用的 V8 堆。
  • 先看趋势,再看分代,最后再抓快照找 Retainers。
  • 关于 V8 堆上限和设计原因,可继续看:v8-memory-limit-and-design