内存泄漏排查
内存泄漏的基本概念
内存泄漏指的是程序中已分配的内存未能被正确释放,导致可用内存逐渐减少。在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内置工具
--inspect
标志启动调试- Chrome DevTools的内存分析器
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);
第三方工具
-
heapdump:生成堆快照
npm install heapdump
const heapdump = require('heapdump'); // 手动生成快照 heapdump.writeSnapshot('/path/to/snapshot.heapsnapshot');
-
v8-profiler:详细的CPU和内存分析
const profiler = require('v8-profiler-next'); const snapshot = profiler.takeSnapshot(); snapshot.export((error, result) => { fs.writeFileSync('snapshot.heapsnapshot', result); snapshot.delete(); });
分析堆快照
- 使用Chrome DevTools加载
.heapsnapshot
文件 - 重点关注:
- 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);
});
}
预防内存泄漏的最佳实践
-
使用严格模式
'use strict';
-
监控内存使用
const memwatch = require('memwatch-next'); memwatch.on('leak', (info) => { console.error('Memory leak detected:', info); });
-
定期压力测试
autocannon -c 100 -d 60 http://localhost:3000
-
代码审查重点关注:
- 全局状态管理
- 事件监听器生命周期
- 大对象缓存策略
- 异步操作完成情况
高级调试技巧
核心转储分析
-
生成核心转储
ulimit -c unlimited node --abort-on-uncaught-exception app.js
-
使用llnode分析
llnode node -c core.1234 > v8 findjsobjects > v8 findjsinstances Array
内存增长对比分析
- 获取初始堆快照
- 执行可疑操作
- 获取后续堆快照
- 比较快照间的对象分配差异
const { writeHeapSnapshot } = require('v8');
const fs = require('fs');
// 第一次快照
writeHeapSnapshot('snapshot1.heapsnapshot');
// 执行操作后
setTimeout(() => {
writeHeapSnapshot('snapshot2.heapsnapshot');
}, 10000);
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn