【Node】生产环境别在 console.log 了

2,953 阅读2分钟

示例

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带来的内存占用问题。