node.js中await会造成内存泄漏?

最小复现代码:

test.js

function sleep(delay) {
    return new Promise(resolve => setTimeout(resolve, delay));
}

const _ = require('lodash');

async function doTask() {
    // console.log('start');
    await sleep(_.random(10, 30));
    // console.log('done');
}

async function start() {
    await sleep(3);
    await doTask();
    await start(); // 去掉前面的await就好了
}

start();

setInterval(() => {
    console.log(process.memoryUsage());
},10e3);

进程启动的时候rss是22M,运行了1个多小时后内存占用达到了380M

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
30211 devel     20   0 1229m 381m  10m S  1.0  9.6   1:36.63 node test.js
# 在start函数的递归调用中取消await
21729 devel     20   0  867m  23m  10m S  1.0  0.6   1:22.73 node test.js
# 改成循环
14498 devel     20   0  867m  24m  10m S  1.0  0.6   1:11.04 node test.js

虽然确定是在递归调用的时候前面多加了个await,但是能解释下原因么,是我使用的姿势不对么

阅读 7.3k
7 个回答

async 函数返回的是一个 Promise 对象,它是否resolved取决于其中的await是否执行完毕;而await 在 Promise 对象 resolved前会一直阻塞代码运行。再看代码,await start() 中start()是一个async函数,返回的是一个pedding状态的promise,而await 会一直等着这个promise直到resolved,导致start一直进栈,没有出栈:
图片描述

去掉await后,不需要等着这个promise resolved,start可以正常出栈:
图片描述

我猜是尾递归的原因

下一次start调用的时候,前一次的promise对象并没有销毁,所以每次都会产生一个新的promise对象,内存可不就泄露了吗

补充2个图,导出堆快照后使用阿里云的nodejs性能平台分析如下:

clipboard.png
clipboard.png

回答中又说尾递归的,有说promise没有销毁的,但是为啥我同样是递归的写成这样就不会有内存泄漏:

async function start() {
    await sleep(3);
    await doTask();
    start(); // 不await了,直接递归
}

await就是等待当前行执行完毕,采取执行下一行

最后await start(),意思是说必须等待我start()调用完毕,能返回一个resolve或者reject,才能执行下一行(下一行没有东西了,也就是退出start的调用栈),而这里递归使用await,就永远无法退出调用栈

相当于下面的不断叠加栈

sleep
.then dotask
.then start
    sleep
    .then dotask
    .then start
        sleep
        ...

如果去掉await
最后一行不需要等待start调用完毕,即可退出当前调用栈

如下

sleep
.then dotask
.then start

sleep
.then dotask
.then start

...

加了await后,当前的start需要等下一个结束,而下一个在等更后面的,所以内存一直没法释放
去了await后,当前的start在开始下一个后就结束了(因为不用等)

每次都会产生一个新的promise对象,内存可不就泄露了吗

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏