← 中文资源

简体中文版本

Node.js — JavaScript heap out of memory

通过堆快照诊断 Node.js 堆耗尽,找到被保留的对象,并修复泄漏而不是只提高堆上限。

阅读英文原文

堆错误是症状

V8 报告存活对象超过堆上限,但不会告诉你哪个结构保留了它们。首先要判断存活集是本来就这么大——例如批处理加载巨大文件——还是每次请求都在增长。在恒定负载下持续攀升且不进入平台期,通常意味着泄漏或无界缓冲。

const cache = new Map(); // unbounded unless entries are evicted
app.get('/user/:id', async (req, res) => {
  const user = await loadUser(req.params.id);
  cache.set(req.params.id, user); // grows with every unique id
  res.json(user);
});

找到保留者

预热后拍一次堆快照,增长期后再拍一次。在 Chrome DevTools 中比较快照并按 retained size 排序。引用链会显示对象为何仍存活:全局 map、事件发射器、闭包或计时器。要修复保留者而不是症状。带大小限制的 LRU 缓存、关闭时移除监听器或流式响应,往往能彻底消除增长。

const cache = new LRUCache({ max: 1000 });

用流代替缓冲

用 await response.arrayBuffer() 读取大文件或代理大响应,会把整个响应体实体化。应使用流和背压。查询结果同理:分页或使用游标,而不是把所有行加载进数组。

修复与验证

为每个长期存活的集合设上限,清理监听器和计时器,并流式处理大负载。通过运行超过原故障窗口的负载并确认内存进入平台期来验证。比较快照,确认主要保留类型停止增长。只有在理解存活集且主机有余量后,才提高堆上限。