帮忙分析下这段 Nodejs 代码的内存泄漏原因
帮忙分析下这段 Nodejs 代码的内存泄漏原因
有一个实时应用,使用 nodejs 编写,会每隔一段时间调用远程 grpc ,大概每秒 1 、2 次这样的调用。上线一个月后发现服务器内存占用越来越大。大概占用了 14GB 的内存吧。用 iotop 发现 node 内存炸了。
使用了 alinode dump heap 了,发现了有一个 promisifyCall 占用了大量内存,疑似泄露。
调用链是
自身大小(字节) 总大小(字节) 函数
0 524600 processTicksAndRejections internal/process/task_queues.js
0 524600 updateStatus 我自己的文件
0 524600 publish 这里调用了 grpc 导出的函数
524600 524600 promisifyCall 这里应该就是泄露的函数了
promisifyCall 来自于 https://github.com/bojand/promisify-call ,看了下是被 grpcCaller 引用的 https://github.com/bojand/grpc-caller 。项目中使用了 grpcCaller 去调用 grpc 方法。
const res = await grpcCallerInstance.publish(req);
接着这个 publish 操作就走到promisifyCall
中去了
promisifyCall 的定义看了下,https://github.com/bojand/promisify-call/blob/master/index.js
const wc = require('with-callback')
/**
- Promisifies the call to <code>fn</code> if appropriate given the arguments.
- Calls the function <code>fn</code> either using callback style if last argument is a function.
- If last argument is not a function, <code>fn</code> is called returning a promise.
- This lets you create API that can be called in either fashions.
- @param {Object} ctx context / this
- @param {Function} fn The function to call
- @param {arguments} args Arguments
- @return {undefined|*|Promise} Promise if promisified
*/
function promisifyCall (ctx, fn) {
const args = []
args.push.apply(args, arguments)
args.splice(0, 2)
// check if last (callback) argument is being pased in explicitly
// as it might be undefined or null, in which case we’ll replace it
const same = fn.length && args.length === fn.length
const lastIndex = same ? fn.length - 1 : args.length - 1
const lastArg = args && args.length > 0 ? args[lastIndex] : null
const cb = typeof lastArg === ‘function’ ? lastArg : null
if (cb) {
return fn.apply(ctx, args)
}
return wc(callback => {
same
? args[lastIndex] = callback
: args.push(callback)
fn.apply(ctx, args)
})
}
隐约感觉里面的 args 变量可能会导致泄露。但还是没想明白怎样才会发生这个泄露。
我可能搞错了,524600 字节并不大。再抓一晚上数据看看。
Node.js 不用 buffer 不是最大内存 1.4GB 吗,兄弟你这 14GB 怎么搞出来的
同时起了多个进程
当然,我很乐意帮你分析Node.js代码的内存泄漏原因。不过,由于我无法直接看到你的代码,我将提供一个常见的Node.js内存泄漏场景的分析,并附上示例代码和解释。
常见内存泄漏场景:未释放的事件监听器
在Node.js中,如果事件监听器没有被正确移除,它们可能会导致内存泄漏。例如:
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
function onEvent() {
console.log('an event occurred!');
// 注意这里没有移除监听器
}
myEmitter.on('event', onEvent);
// 假设这里有一些代码不断触发事件
setInterval(() => {
myEmitter.emit('event');
}, 100);
// 由于onEvent监听器没有被移除,如果这段代码长时间运行,
// 'onEvent'函数和相关的上下文(如闭包中的变量)将一直保留在内存中,导致内存泄漏。
解决方法
为了解决这个问题,你应该在不再需要监听器时移除它:
const removeListener = myEmitter.on('event', onEvent);
// 当不再需要监听时
removeListener();
或者使用once
方法,它会在事件触发后自动移除监听器:
myEmitter.once('event', onEvent);
在实际代码中,内存泄漏的原因可能更加复杂,涉及未释放的定时器、缓存未清理、大型数据结构未释放等。使用工具如Node.js的--inspect
标志、Chrome DevTools或内存分析工具(如heapdump
和memwatch-next
)可以帮助你更准确地诊断问题。