示例
while (true) {
console.log("Hello, World!");
}
不久之后输出
...
Hello, World!
Hello, World!
Hello, World!
Hello, World!
Hello, World!
<--- Last few GCs --->
710976 ms: Mark-sweep 698.3 (737.7) -> 698.3 (737.7) MB, 15343.7 / 0 ms [allocation failure] [GC in old space requested].
726188 ms: Mark-sweep 698.3 (737.7) -> 698.3 (737.7) MB, 15212.6 / 0 ms [allocation failure] [GC in old space requested].
741831 ms: Mark-sweep 698.3 (737.7) -> 698.3 (737.7) MB, 15642.7 / 0 ms [last resort gc].
757723 ms: Mark-sweep 698.3 (737.7) -> 698.3 (737.7) MB, 15892.2 / 0 ms [last resort gc].
<--- JS stacktrace --->
==== JS stack trace =========================================
Security context: 0x37c258b9 <JS Object>
1: nextTick [node.js:~465] [pc=0x341fac9c] (this=0x37c7f939 <a process with map 0x563136b1>,callback=0x3e7bf5e1 <JS Function afterWrite (SharedFunctionInfo 0x3e777679)>)
2: arguments adaptor frame: 5->1
3: onwrite(aka onwrite) [_stream_writable.js:~314] [pc=0x341f3f50] (this=0x37c08099 <undefined>,stream=0x3e7bf6b1 <a WriteStream with map 0x56322bc9>,er=0x37c08099 <undefined>)
4: _w...
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - process out of memory
Aborted
在node环境,仅仅是输出个string,为什么会内存不足?这是一个已知的"问题",因为在tty / console的情况下写入stdout是异步的。因此,如果tty / console无法跟上,那么非常快速地记录大量数据很可能会导致大量写入缓冲在内存中。
console的关键代码如下
// internal/console/constructor.js
Console.prototype[kBindStreamsLazy] = function(object) {
let stdout;
let stderr;
Object.defineProperties(this, {
'_stdout': {
enumerable: false,
configurable: true,
get() {
if (!stdout) stdout = object.stdout;
return stdout;
},
set(value) { stdout = value; }
},
'_stderr': {
enumerable: false,
configurable: true,
get() {
if (!stderr) { stderr = object.stderr; }
return stderr;
},
set(value) { stderr = value; }
}
});
};
着重观察下 _stdout对象。当我们运行js代码,执行console.log的时候,就会调用_stdout对象。
那么,内容在输出时,是缓存在哪里呢?
请继续看下面的代码
// _http_common.js
const parsers = new FreeList('parsers', 1000, function parsersCb() {
const parser = new HTTPParser();
cleanParser(parser);
parser.onIncoming = null;
parser[kOnHeaders] = parserOnHeaders;
parser[kOnHeadersComplete] = parserOnHeadersComplete;
parser[kOnBody] = parserOnBody;
parser[kOnMessageComplete] = parserOnMessageComplete;
return parser;
});
当请求进来时,上面的new FreeList会申请大小为1000的缓冲区,我们再看看freelist干了什么
// freelist.js
constructor(name, max, ctor) {
this.name = name;
this.ctor = ctor;
this.max = max;
this.list = [];
}
...
free(obj) {
if (this.list.length < this.max) {
this.list.push(obj);
return true;
}
return false;
}
调用free的时候,会将obj压入list。
当程序在运行时,如果使用console.log输出内容很频繁,还没有gc前,内存占用会很高。
结论:生产环境最好不要console.log,可以将内容输出到文件,就可以解决console.log带来的内存占用问题。