一、内存泄漏的那些事儿
咱们程序员最怕什么?代码跑着跑着,内存蹭蹭往上涨,最后直接把服务给干趴了。这种场景在Node.js里特别常见,毕竟它单线程的架构,一旦内存泄漏就是"一锅端"。
举个例子,你写了个简单的HTTP服务:
const http = require('http');
// 错误示范:全局变量存储请求数据
let cache = [];
http.createServer((req, res) => {
// 每次请求都把body存到全局数组
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
cache.push(body); // 内存泄漏点!
res.end('OK');
});
}).listen(3000);
看见没?这个cache数组会无限制增长,请求越多内存占用越大。这就是典型的内存泄漏——该释放的内存没释放。
二、如何揪出内存泄漏
2.1 Chrome DevTools大法
Node.js和Chrome同宗同源,调试工具都是通用的:
# 启动时加上--inspect
node --inspect server.js
然后在Chrome地址栏输入chrome://inspect,抓取内存快照。重点看:
- Retained Size大的对象
- 重复出现的相似对象
- 脱离DOM树的游离节点(前端场景)
2.2 内存dump分析
用heapdump模块拍快照:
const heapdump = require('heapdump');
// 手动触发dump
setInterval(() => {
heapdump.writeSnapshot((err, filename) => {
console.log(`Dump saved to ${filename}`);
});
}, 60 * 1000); // 每分钟dump一次
用clinic.js工具分析:
clinic heapdoctor -- node server.js
三、常见泄漏场景与修复
3.1 闭包陷阱
看这段代码:
function createClosure() {
const hugeArray = new Array(1000000).fill('*');
return function() {
console.log('This holds the array!');
};
}
setInterval(() => {
const fn = createClosure();
// fn虽然没执行,但hugeArray被闭包引用着
}, 1000);
修复方案:
function safeClosure() {
const tempArray = new Array(1000000).fill('*');
// 使用完后主动释放
return function() {
tempArray.length = 0; // 清空数组
console.log('Array cleared');
};
}
3.2 事件监听泄漏
const EventEmitter = require('events');
const emitter = new EventEmitter();
function createListener() {
emitter.on('ping', () => {
console.log('Memory leaking...');
});
}
// 每次调用都新增监听器
setInterval(createListener, 1000);
正确做法:
// 方案1:使用once替代on
emitter.once('ping', handler);
// 方案2:主动移除监听器
const handler = () => {...};
emitter.on('ping', handler);
emitter.removeListener('ping', handler);
四、高级防御策略
4.1 内存限制
启动时设置内存上限:
node --max-old-space-size=512 server.js
程序内监控:
setInterval(() => {
const usage = process.memoryUsage();
if (usage.heapUsed > 500 * 1024 * 1024) {
console.error('Memory overload!');
process.exit(1); // 自杀保平安
}
}, 5000);
4.2 Worker线程隔离
把危险操作放到Worker线程:
const { Worker } = require('worker_threads');
function runTask() {
const worker = new Worker(`
const { parentPort } = require('worker_threads');
// 内存密集型操作放这里
parentPort.postMessage('done');
`);
worker.on('exit', () => {
console.log('Worker terminated');
});
}
五、实战经验总结
- 定时重启:用
pm2设置定时重启 - 监控报警:接入Grafana监控堆内存
- 代码规范:
- 避免大对象全局存储
- 及时清理定时器
- 慎用第三方库(有些库会悄悄缓存数据)
记住,内存泄漏就像房间里的垃圾,不打扫就会越堆越多。定期"大扫除",才能让Node.js应用跑得又快又稳!
评论