帮忙分析下这段 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 变量可能会导致泄露。但还是没想明白怎样才会发生这个泄露。


4 回复

我可能搞错了,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或内存分析工具(如heapdumpmemwatch-next)可以帮助你更准确地诊断问题。

回到顶部