内存泄漏检测与优化
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 操作:
- 打开
chrome://inspect - 点击
Open dedicated DevTools for Node - 进入 Memory 面板
- 选择
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)
- Summary 视图:按构造函数分组,查看对象数量
- Comparison 视图:对比两个快照,找出增长对象
- Containment 视图:查看对象引用链
- 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兜底
📝 发现内容有误?点击此处直接编辑