简单的 nodejs 应用程序中的内存泄漏

Posted

技术标签:

【中文标题】简单的 nodejs 应用程序中的内存泄漏【英文标题】:Memory Leaks in simple nodejs app 【发布时间】:2016-10-04 13:10:39 【问题描述】:

只是为了好玩和尝试 nodejs,我编写了一个非常非常简单的程序来测试 Collat​​z 猜想的数量荒谬。从理论上讲,这应该没问题。我遇到的问题是这个超级简单的代码有内存泄漏,我无法确定原因。

var step;
var numberOfSteps;
for (var i = 0; i < 100000000000000; i++) 
    step = i;
    numberOfSteps = 0;
    while (step !== 1) 
        if (step%2 === 0)
            step /= 2;
        else
            step = 3 * step + 1;
        numberOfSteps++;
    
    console.log("" + i + ": " + numberOfSteps + " steps.");

我已经尝试了循环内外的变量。我试过在循环结束时将它们归零。没有什么能改变内存泄漏。

【问题讨论】:

泄漏点在哪里?我在电脑上试了一下,内存只增加了不到0.01G 没有泄漏...但是对于 step === 0 的 while 循环是 infinite ...但是,如果您解决了该问题,节点似乎确实会保留慢慢地吞噬记忆,不是吗 console.log 造成的 - 就好像 GC 无法运行以清理 console.log 调用留下的垃圾 我无法在我的情况下运行它,运行 2-3 分钟后它坏了并且崩溃了:FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - process out of memory Aborted (core dumped)(核心转储更大)。虽然我正在检查内存使用情况,但它增加了大约 800mb,后来保持稳定,它继续运行并与上面的核心转储崩溃。 应该从 i=1 开始,我只是把我留下的数字放在那里。超过 1 时,它永远不应低于 1。 【参考方案1】:

调查一下我的核心转储:

<--- Last few GCs --->

  131690 ms: Scavenge 1398.1 (1458.1) -> 1398.1 (1458.1) MB, 1.3 / 0 ms (+ 2.8 ms in 1 steps since last GC) [allocation failure] [incremental marking delaying mark-sweep].
  132935 ms: Mark-sweep 1398.1 (1458.1) -> 1398.1 (1458.1) MB, 1245.0 / 0 ms (+ 3.7 ms in 2 steps since start of marking, biggest step 2.8 ms) [last resort gc].
  134169 ms: Mark-sweep 1398.1 (1458.1) -> 1398.1 (1458.1) MB, 1234.5 / 0 ms [last resort gc].


<--- JS stacktrace --->

==== JS stack trace =========================================

Security context: 0x33083d8e3ac1 <JS Object>
    1: /* anonymous */ [/user/projects/test.js:~1] [pc=0x557d307b271] (this=0x2a4a669d8341 <an Object with map 0xf8593408359>,exports=0x33083d804189 <undefined>,require=0x33083d804189 <undefined>,module=0x33083d804189 <undefined>,__filename=0x33083d804189 <undefined>,__dirname=0x33083d804189 <undefined>)
    3: _compile [module.js:413] [pc=0x557d304d03c] (this=0x2a4a669d8431...

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - process out of memory
Aborted (core dumped)

这似乎是关于console.log的一个已知问题,根据github上的这个问题https://github.com/nodejs/node/issues/3171

这是一个已知的“问题”,因为在 tty/控制台是异步的。所以非常快速地记录大量数据可以非常 如果 tty/console 会导致大量写入缓存在内存中 跟不上。

【讨论】:

【参考方案2】:

这里的代码对我来说似乎达到了大约 50MB 的峰值

这会执行大量 10000 个功能 - 使用 setImmediate 来处理下一批

function collatz(n) 
    var step,numberOfSteps, i;
    for(i = 0; i < 10000; i++, n++) 
        step = n;
        numberOfSteps = 0;
        while (step !== 1) 
            if (step%2 === 0)
                step /= 2;
            else
                step = 3 * step + 1;
            numberOfSteps++;
        
        console.log("" + n + ": " + numberOfSteps + " steps.");
    
    if (n < 100000000000000) 
        setImmediate(collatz, n);
    

collatz(1);

注意,在这种情况下,你可以让 for 循环从 0 开始,因为 n 将从 1 开始:p

我没有尝试过更高的 for 循环值

我已经对原始代码进行了一些基准测试 - 一次执行 100 个(在 for 循环中)提供与 10000 个相同的性能,并且在性能上与原始代码没有区别。即使一次 10 个,我也不会说这种方法更慢。每次只有 1 次,它始终比原始代码慢 5-8%

注意,我最初认为问题是垃圾收集(或缺乏垃圾收集),因为紧密的循环让节点没有时间做任何家务,但是当我发布答案时,@Svabel 发布了似乎是已知的用力击中 console.log 的问题。

我只能假设使用 setImmediate 允许对 tty 缓冲区进行某种内部管理,否则这是不可能的。

【讨论】:

你能解释一下为什么让它异步可以解决内存问题吗? 不是真的,我认为这可能是一个 GC 问题,但阅读@Svabel 的回答,这可能与关于 console.log 的“已知问题”有关 - 我不想将其包含在我的答案中,因为这似乎不合适 很有趣,我在那个“问题”中看到,setImmediate 的使用是一个建议的解决方案 - 每次迭代一个 setImmediate - 每次迭代 10000 是多余的,即使 100 给出类似的(实际上在几个测试,我不能说它比原始代码更差)“性能” - 而每次迭代 1 确实对速度有大约 8% 的影响

以上是关于简单的 nodejs 应用程序中的内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

NodeJS 内存增长(内存泄漏)

nodejs setMaxListeners 避免内存泄漏检测

带有 Socket.IO 1.0 的 NodeJS - 堆外的内存泄漏

如何自己检查NodeJS的代码是不是存在内存泄漏

Android技术分享|Android 中部分内存泄漏示例及解决方案

QByteArray导致的内存泄漏问题