阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 内存泄漏排查

内存泄漏排查

作者:陈川 阅读数:12961人阅读 分类: Node.js

内存泄漏的基本概念

内存泄漏指的是程序中已分配的内存未能被正确释放,导致可用内存逐渐减少。在Node.js中,内存泄漏可能导致应用性能下降甚至崩溃。常见的内存泄漏场景包括全局变量滥用、闭包未释放、事件监听未移除等。

常见的内存泄漏场景

全局变量滥用

// 错误示例
function leak() {
  leakedArray = new Array(1000000).fill('*'); // 未使用var/let/const声明
}

// 正确做法
function noLeak() {
  const localArray = new Array(1000000).fill('*');
}

全局变量会一直存在于内存中,直到进程结束。在Node.js中,模块级别的变量实际上也是"半全局"的,需要注意其生命周期。

未清理的定时器

// 泄漏示例
setInterval(() => {
  const data = fetchData();
  processData(data);
}, 1000);

// 正确做法
const intervalId = setInterval(/* ... */);
// 需要时清除
clearInterval(intervalId);

未清除的定时器会阻止相关闭包中的变量被垃圾回收。

事件监听器未移除

const EventEmitter = require('events');
const emitter = new EventEmitter();

function createListener() {
  const heavyObject = new Array(1000000).fill('*');
  emitter.on('event', () => {
    console.log(heavyObject.length);
  });
}

// 正确做法
function createProperListener() {
  const heavyObject = new Array(1000000).fill('*');
  const listener = () => {
    console.log(heavyObject.length);
  };
  emitter.on('event', listener);
  // 需要时移除
  return () => emitter.off('event', listener);
}

内存泄漏检测工具

Node.js内置工具

  1. --inspect标志启动调试
  2. Chrome DevTools的内存分析器
  3. process.memoryUsage()API
setInterval(() => {
  const memoryUsage = process.memoryUsage();
  console.log(`RSS: ${memoryUsage.rss / 1024 / 1024} MB`);
  console.log(`HeapTotal: ${memoryUsage.heapTotal / 1024 / 1024} MB`);
  console.log(`HeapUsed: ${memoryUsage.heapUsed / 1024 / 1024} MB`);
}, 1000);

第三方工具

  1. heapdump:生成堆快照

    npm install heapdump
    
    const heapdump = require('heapdump');
    
    // 手动生成快照
    heapdump.writeSnapshot('/path/to/snapshot.heapsnapshot');
    
  2. v8-profiler:详细的CPU和内存分析

    const profiler = require('v8-profiler-next');
    const snapshot = profiler.takeSnapshot();
    snapshot.export((error, result) => {
      fs.writeFileSync('snapshot.heapsnapshot', result);
      snapshot.delete();
    });
    

分析堆快照

  1. 使用Chrome DevTools加载.heapsnapshot文件
  2. 重点关注:
    • Retained Size:对象及其引用链占用的总内存
    • Shallow Size:对象自身占用的内存
    • Distance:与GC roots的距离

常见可疑对象:

  • 大数组
  • 闭包中的大对象
  • 未释放的缓存

实战案例分析

案例1:缓存未清理

const cache = {};

function processRequest(req) {
  if (!cache[req.url]) {
    cache[req.url] = generateResponse(req);
  }
  return cache[req.url];
}

改进方案:

const LRU = require('lru-cache');
const cache = new LRU({
  max: 100,  // 最大缓存项数
  maxAge: 1000 * 60 * 5 // 5分钟
});

function processRequest(req) {
  if (!cache.has(req.url)) {
    cache.set(req.url, generateResponse(req));
  }
  return cache.get(req.url);
}

案例2:Promise未处理

function asyncOperation() {
  return new Promise((resolve) => {
    // 忘记调用resolve
    // resolve(data);
  });
}

// 累积的未完成Promise会占用内存
setInterval(asyncOperation, 100);

解决方案:

function asyncOperation() {
  return new Promise((resolve) => {
    resolve();
  }).catch(err => {
    console.error(err);
  });
}

预防内存泄漏的最佳实践

  1. 使用严格模式

    'use strict';
    
  2. 监控内存使用

    const memwatch = require('memwatch-next');
    memwatch.on('leak', (info) => {
      console.error('Memory leak detected:', info);
    });
    
  3. 定期压力测试

    autocannon -c 100 -d 60 http://localhost:3000
    
  4. 代码审查重点关注:

    • 全局状态管理
    • 事件监听器生命周期
    • 大对象缓存策略
    • 异步操作完成情况

高级调试技巧

核心转储分析

  1. 生成核心转储

    ulimit -c unlimited
    node --abort-on-uncaught-exception app.js
    
  2. 使用llnode分析

    llnode node -c core.1234
    > v8 findjsobjects
    > v8 findjsinstances Array
    

内存增长对比分析

  1. 获取初始堆快照
  2. 执行可疑操作
  3. 获取后续堆快照
  4. 比较快照间的对象分配差异
const { writeHeapSnapshot } = require('v8');
const fs = require('fs');

// 第一次快照
writeHeapSnapshot('snapshot1.heapsnapshot');

// 执行操作后
setTimeout(() => {
  writeHeapSnapshot('snapshot2.heapsnapshot');
}, 10000);

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

上一篇:性能分析工具

下一篇:CPU性能分析

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌