nodejs中console.log的循环

Loop of console.log in nodejs

我的MCVE如下

var i = 0;
for(;;)
    console.log(i++)

当我执行此操作时,在某个时刻,我的 nodejs 只是停止打印内容,给我的输出如下所示

[...]
684665
684666
684667

然后,我得到了这个:

<--- Last few GCs --->

   69097 ms: Scavenge 1397.2 (1456.7) -> 1397.2 (1456.7) MB, 0.8 / 0 ms (+ 1.7 ms in 1 steps since last GC) [allocation failure] [incremental marking delaying mark-sweep].
   70462 ms: Mark-sweep 1397.2 (1456.7) -> 1396.0 (1456.7) MB, 1364.9 / 0 ms (+ 2.8 ms in 2 steps since start of marking, biggest step 1.7 ms) [last resort gc].
   71833 ms: Mark-sweep 1396.0 (1456.7) -> 1397.1 (1456.7) MB, 1370.2 / 0 ms [last resort gc].


<--- JS stacktrace --->

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

Security context: 0xcdf79d37399 <JS Object>
    1: formatPrimitive(aka formatPrimitive) [util.js:~411] [pc=0x634d9f4113f] (this=0xcdf79d04131 <undefined>,ctx=0x17b18f4d561 <an Object with map 0x32fd25043ef9>,value=16248021)
    2: formatValue(aka formatValue) [util.js:223] [pc=0x634d9f1fdbb] (this=0xcdf79d04131 <undefined>,ctx=0x17b18f4d561 <an Object with map 0x32fd25043ef9>,value=16248021,recurseTimes=2)
    3: inspect(aka inspect) [uti...

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - process out of memory
[1]    19446 abort (core dumped)  node

我想知道,console.log 做什么可能会导致内存不足错误?

根据这个讨论:https://groups.google.com/forum/#!topic/nodejs/KtONbpVV68U

每次调用console.log(或其他日志记录方法)时,控制台对象都会分配一些内存。垃圾收集器将在下一次滴答时释放该内存。但是如果你有一个太大的循环,下一个报价永远不会到来。

但是我测试了这个:

setInterval(function() {
    for (var i = 0; i < 100000; ++i) {
        console.log(i);
    }
}, 10000)

根据 google 组讨论,在每个间隔之间,NodeJS 垃圾收集器应释放由 console.log 分配的内存。但事实并非如此。每次循环 运行,我的进程占用更多 RAM。

我也测试过这个:

var fs = require('fs')
var output = fs.createWriteStream('./stdout.log');
var errorOutput = fs.createWriteStream('./stderr.log');
var logger = new console.Console(output, errorOutput);

var i = 0;
for (;;) {
    logger.log(i++);
}

行为相同。该进程占用越来越多的 RAM 直到它崩溃(因为没有更多的 RAM 可用)。并且文件 stdout.log 始终为空。

最后,我测试了这个:

var fs = require('fs')
var output = fs.createWriteStream('./stdout.log');
var errorOutput = fs.createWriteStream('./stderr.log');
var logger = new console.Console(output, errorOutput);

setInterval(function() {
    for (var i = 0; i < 100000; i++) {
        logger.log(i);
    }
}, 5000)

这个例子很有趣。因为在每个间隔之间,stdout.log 被写入(行附加到文件)并且进程回收的 RAM 不会增长。在每个间隔之间,垃圾收集器执行其工作。

我认为控制台对象不能很好地处理缓冲区。但这仍然很奇怪。如果你使用标准输出(只有一个 console.log),它看起来控制台对象保存在内存中到目前为止打印的所有内容。它永远不会净化。如果你使用文件输出,一切正常(当然,除非你在无限循环中编写它)。

可能是因为 NodeJS 版本的原因,我正在使用 NodeJS 0.12.7

在 nodejs 存储库上打开一个问题后,我得到了 following answer :

It's expected behavior: console.log is asynchronous, the memory associated with each call cannot be reclaimed until the next tick of the event loop. In your example that next tick never happens because of the infinite loop. If you rewrite your example to a callback-driven approach, it keeps running forever:

 let i = 0;
 const next = () => process.stdout.write(`${i++}\n`, next);
 next();

https://github.com/nodejs/node/issues/11568

的讨论中查看更多详细信息

此外,如文档中所述,当 stdout 重定向到文件时,控制台是 同步。但在这种情况下,描述的行为仍然重现。

console.log() 输出重定向到文件的无限循环不会使节点 v4.x 崩溃,但会使节点 v6 崩溃。 这似乎是 v6 中引入的错误。

作为临时解决方法,可以使用 console-sync 补丁。