无限循环中的NodeJS内存消耗
Posted
技术标签:
【中文标题】无限循环中的NodeJS内存消耗【英文标题】:NodeJS memory consumption in an infinite loop 【发布时间】:2011-12-15 14:55:36 【问题描述】:我不知道这是 Node 还是 V8 的错误,但如果我运行以下代码,节点进程会泄漏内存。 GC 似乎永远不会启动,并且在几秒钟内它会消耗超过 1GB 的内存。这是意外行为。我错过了什么吗?
代码如下:
for(;;) console.log(1+1);
显然,这有点人为的情况,但我可以看到一个长期运行的进程永远不会释放内存的问题。
编辑:我尝试了 v0.5.10(不稳定)和 v0.4.12(稳定),不稳定版本的性能稍微好一点——稳定版本只是停止输出到控制台但继续消耗内存,而稳定版继续执行和消耗内存,没有暂停。
【问题讨论】:
经过一些研究,我无法准确找出为什么节点在没有 GC 的情况下会消耗越来越多的内存。一般来说,在 node.js 中使用任何类型的无限循环都是 bad 的想法。必须了解 node.js 脚本在执行输入脚本后实际上已经在运行一个事件循环(可能是无限的),因此没有理由在等待某事发生时模拟这样的循环。 (来自 nodejs.org)“节点只是在执行输入脚本后进入事件循环。当没有更多回调要执行时,节点退出事件循环。[...] 事件循环对用户隐藏。” @WTK 我知道事件循环或多或少是一个无限循环,但这种结构并不是为了保持循环存活;我在编写守护程序时偶然发现了它。此代码还有其他变体,例如使用 setInterval 以 1 毫秒的延迟重复调用回调也会导致此问题。我在 Python 和 Ruby 下尝试了类似的代码,它们的内存占用都小于 10MB。我可以想象,在负载下长时间运行的守护进程会发生类似的内存泄漏,尽管我还没有测试过。 您对触发此问题的其他可能方式是正确的,但它们基本上都是阻塞无限循环。我很确定每个经过良好测试、稳定、长时间运行的库都依赖于 node.js“本机”事件循环,而不是手动创建的某种阻塞无限循环。无论如何,我希望有人能阐明这种无限循环中发生了什么,以及为什么内存使用量不断增加。 @WTK 我同意你的看法;阻塞调用也会影响执行速度——大约是可比较的 Python 或 Ruby 代码的 1/6。我想知道在看到这个之后它是否足够稳定和安全,可以在生产中使用。 您应该将此作为错误提交。 【参考方案1】:由于 Node.js v0.10 已经发布,调用递归回调时,setImmediate
应作为首选,而不是process.nextTick
。
function loginf()
console.log(1+1);
setImmediate(loginf);
loginf();
在我的电脑上运行大约 15 分钟后,这段代码的内存消耗保持在较低水平 (
相反,无限运行for loop
会导致内存泄漏,process.nextTick
会抛出Maximum call stack size exceeded
错误。
也请查看此问答:setImmediate vs. nextTick
【讨论】:
【参考方案2】:@VyacheslavEgorov 的答案似乎是正确的,但我想推迟到事件循环会解决问题。您可能想比较一下您的无限 for-loop
与此无限循环策略的比较:
function loginf()
console.log(1+1);
process.nextTick(loginf);
loginf();
这个想法是使用process.nextTick(cb)
来推迟事件循环并(大概)允许 GC 做它的事情。
【讨论】:
这是内存的显着改进,而且 IIRC 也稍微快了一点。我很惊讶 setInterval 也不能这样工作。调用具有短暂延迟的函数会导致相同的内存泄漏。我想知道这些是否是 Node 中的设计问题。 @Matty:nextTick
的文档说“这不是 setTimeout(fn, 0) 的简单别名,效率更高”,所以看起来像明确的设计选择。【参考方案3】:
您将阻止 node.js 事件循环,因为您永远不会返回它。
当您向流中写入内容时,node.js 会异步执行此操作:它发送写入请求,在流的内部数据结构中排列有关已发送请求的信息,并等待通知它完成的回调。
如果您阻止事件循环,则永远不会调用回调(因为永远不会处理传入事件),并且永远不会释放流中排队的辅助数据结构。
如果您通过不断地使用 nextTick/setInterval/setTimeout 安排自己的事件来“超载”事件循环,则可能会发生同样的情况。
【讨论】:
那么基本上GC只发生在事件循环循环的时候? @Matty GC 实际上会在堆增长时发生很多次。但它不能收集活动的东西:写请求辅助结构由输出流在内部保存,直到写后回调被调用......这永远不会发生。由于事件循环被阻塞,输出流不理解他从底层请求的 IO 操作已完成,并且不再需要这些结构。以上是关于无限循环中的NodeJS内存消耗的主要内容,如果未能解决你的问题,请参考以下文章