全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-14 10 分钟 ✍️ juanwangdev

内存泄漏检测与优化

Node.js 基于 V8 引擎,内存泄漏会导致进程内存持续增长,最终触发 OOM 崩溃。

内存泄漏常见原因

1. 全局变量累积

JavaScript
// 错误:全局数组持续增长
const cache = [];
function addToCache(data) {
  cache.push(data);  // 永不清理
}

// 正确:限制缓存大小
const cache = [];
const MAX_CACHE_SIZE = 1000;
function addToCache(data) {
  if (cache.length >= MAX_CACHE_SIZE) {
    cache.shift();  // 移除最旧项
  }
  cache.push(data);
}

2. 闭包引用未释放

JavaScript
// 错误:闭包持有大对象引用
function createHandler() {
  const largeData = new Array(1000000).fill('x');
  return function() {
    console.log(largeData.length);  // largeData 永不释放
  };
}

// 正确:只保留必要数据
function createHandler() {
  const largeData = new Array(1000000).fill('x');
  const length = largeData.length;  // 只保留需要的值
  return function() {
    console.log(length);
  };
}

3. 事件监听器未移除

JavaScript
// 错误:重复添加监听器
function setupListener() {
  emitter.on('data', handler);  // 每次调用都添加
}

// 正确:先移除再添加
function setupListener() {
  emitter.removeListener('data', handler);
  emitter.on('data', handler);
}

// 或使用 once
emitter.once('data', handler);

4. 定时器未清理

JavaScript
// 错误:定时器永不清理
function startPolling() {
  setInterval(() => {
    fetchData();
  }, 1000);
}

// 正确:保存引用并清理
let timer;
function startPolling() {
  timer = setInterval(() => {
    fetchData();
  }, 1000);
}
function stopPolling() {
  clearInterval(timer);
  timer = null;
}

5. Map/Set 无限增长

JavaScript
// 错误:Map 无限增长
const userSessions = new Map();
function addSession(userId, session) {
  userSessions.set(userId, session);  // 永不删除
}

// 正确:使用 WeakMap 或定期清理
const userSessions = new WeakMap();  // 键可被垃圾回收

// 或实现 LRU 缓存
const LRU = require('lru-cache');
const cache = new LRU({ max: 500 });

内存检测工具

1. process.memoryUsage()

JavaScript
// 实时监控内存
function logMemory() {
  const used = process.memoryUsage();
  console.log({
    rss: `${(used.rss / 1024 / 1024).toFixed(2)} MB`,
    heapTotal: `${(used.heapTotal / 1024 / 1024).toFixed(2)} MB`,
    heapUsed: `${(used.heapUsed / 1024 / 1024).toFixed(2)} MB`,
    external: `${(used.external / 1024 / 1024).toFixed(2)} MB`
  });
}
setInterval(logMemory, 5000);

2. Node.js 内置 Inspector

Bash
# 启动带调试端口的应用
node --inspect app.js

# 或暴露给外部
node --inspect=0.0.0.0:9222 app.js

Chrome DevTools 操作:

  1. 打开 chrome://inspect
  2. 点击 Open dedicated DevTools for Node
  3. 进入 Memory 面板
  4. 选择 Take heap snapshot 进行堆快照分析

3. heapdump 模块

JavaScript
const heapdump = require('heapdump');

// 手动生成快照
heapdump.writeSnapshot('/tmp/' + Date.now() + '.heapsnapshot');

// 信号触发
process.on('SIGUSR2', () => {
  heapdump.writeSnapshot('/tmp/' + Date.now() + '.heapsnapshot');
});

4. memwatch-next

JavaScript
const memwatch = require('memwatch-next');

// 监控内存泄漏
memwatch.on('leak', (info) => {
  console.log('Memory leak detected:', info);
});

// 堆差异对比
const hd = new memwatch.HeapDiff();
// 执行可疑操作
const diff = hd.end();
console.log(diff);

5. clinic.js 工具集

Bash
# 安装
npm install -g clinic

# 内存分析
clinic heapprofiler -- node app.js

# 生成火焰图
clinic flame -- node app.js

# 医生诊断
clinic doctor -- node app.js

堆快照分析

获取快照

JavaScript
const v8 = require('v8');
const fs = require('fs');

// 写入堆快照
const snapshotStream = v8.getHeapSnapshot();
const fileStream = fs.createWriteStream('heap.heapsnapshot');
snapshotStream.pipe(fileStream);

分析快照(Chrome DevTools)

  1. Summary 视图:按构造函数分组,查看对象数量
  2. Comparison 视图:对比两个快照,找出增长对象
  3. Containment 视图:查看对象引用链
  4. Statistics 视图:内存分布统计

关键指标:

  • Shallow Size:对象自身占用内存
  • Retained Size:对象及其引用链总内存
  • Distance:距 GC 根的距离

内存优化策略

1. 流式处理大数据

JavaScript
// 错误:一次性加载全部数据
const data = fs.readFileSync('large.json');
const parsed = JSON.parse(data);

// 正确:流式处理
const { pipeline } = require('stream');
const fs = require('fs');

pipeline(
  fs.createReadStream('large.json'),
  JSONStream.parse('*'),
  processStream,
  (err) => console.log('Done')
);

2. 对象池模式

JavaScript
class ObjectPool {
  constructor(createFn, resetFn) {
    this.pool = [];
    this.createFn = createFn;
    this.resetFn = resetFn;
  }

  acquire() {
    return this.pool.length > 0
      ? this.resetFn(this.pool.pop())
      : this.createFn();
  }

  release(obj) {
    this.pool.push(obj);
  }
}

3. 及时释放引用

JavaScript
// 处理完成后置空
let largeData = loadData();
processData(largeData);
largeData = null;  // 允许 GC 回收

4. 限制缓存策略

JavaScript
const LRU = require('lru-cache');

const cache = new LRU({
  max: 500,              // 最大条目数
  maxAge: 1000 * 60 * 5  // 5分钟过期
});

生产环境监控

PM2 内存监控

JavaScript
// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'app',
    script: 'app.js',
    max_memory_restart: '500M',  // 内存超限重启
    node_args: '--max-old-space-size=4096'
  }]
}

APM 工具集成

JavaScript
// New Relic
require('newrelic');

// Datadog
const tracer = require('dd-trace').init();

// AppDynamics
require('appdynamics').profile({
  controllerHostName: 'controller',
  port: 443,
  accountName: 'account',
  accountAccessKey: 'key',
  appName: 'app'
});

注意:V8 默认堆内存限制约 1.4GB(64位系统),可通过 --max-old-space-size 调整。

要点总结

  • 全局变量、闭包、事件监听器、定时器是内存泄漏常见来源
  • 使用 Chrome DevTools 或 clinic.js 进行堆快照分析
  • 大数据处理使用流式方式,避免一次性加载
  • 缓存使用 LRU 策略限制大小
  • 生产环境配置 max_memory_restart 兜底

📝 发现内容有误?点击此处直接编辑

← 上一篇 依赖管理(npm audit)
下一篇 → 进程管理与稳定性(PM2)
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库