Node.js中扩展内存那些事
发布于 2 个月前 作者 blackmatch 548 次浏览 来自 分享

前言

Node.js使用的是Google的V8作为JavaScript脚本引擎,由于V8引擎的限制,Node.js中只能使用部分内存:64位操作系统下约为1.4G,32位操作系统下约为0.7G。对于浏览器来说,这样的限制影响不大,但是对于服务端程序来说有时候可能就不能满足需求了。虽然大内存的使用场景也相对较少,但还是会存在一些默认情况下非代码因素造成的内存溢出问题。

通用的解决方案

在网上搜到的大部分答案都是使用--max_old_space_size这个flag来扩展Node.js可使用的最大的老生代的内存。比如:

node --max_old_space_size=2048 xxx.js

单位是MB,比如上述例子是将最大可用内存扩展至2G。

如果你使用的是Node8.x及以上的版本,还可以通过NODE_OPTIONS这个系统环境变量来配置,例如:

export NODE_OPTIONS=--max_old_space_size=2048

必须要确认该系统环境变量已经生效了,可以用echo $NODE_OPTIONS来校验。

此外,使用Buffer不受V8内存限制

验证扩展内存是否生效

通过v8模块的getHeapStatistics()方法即可。

const v8 = require('v8')

console.log(v8.getHeapStatistics())

执行node --max_old_space_size=2048 test.js结果如下:

{
  total_heap_size: 5009408,
  total_heap_size_executable: 1048576,
  total_physical_size: 3449648,
  total_available_size: 2194521344,
  used_heap_size: 2411032,
  heap_size_limit: 2197815296,
  malloced_memory: 8192,
  peak_malloced_memory: 410288,
  does_zap_garbage: 0
}

其中的heap_size_limit就是老生代可以使用的最大内存,单位是Byte

注意:Node8.x以下版本使用这个方法有bug:当设置的内存超过3.7G时,打印出来的结果是有问题的。

一些特殊情况

在实际的项目中,一般不会很简单的执行node xxx.js来启动项目,有些可能通过pm2等工具来部署,有些可能通过自己写的npm命令行工具来启动,而恰好这个时候使用的Node.js版本是低于8.x版本,这种情况下就只剩下添加--max_old_space_size这个flag来扩展内存了,但是怎么加还是有讲究的,比如一个npm命令行模块的入口文件的第一行通常是:

#!/usr/bin/env node

这样子的,所以我们很容易就想到:

#!/usr/bin/env node --max_old_space_size=2048

通过这样的方式来扩展内存,但事实上这种方式是有很大的局限性,甚至还有可能导致程序卡死。比如如果源代码这样写:

#!/usr/bin/env node --max_old_space_size=2048

const program = require('commander')

program
  .version('0.1.0')
  .command('start')
  .action(function () {
    // start process...
  })

program.parse(process.argv)

...

假设我们这个包名叫blog,在服务器上全局安装这个包后执行blog start命令就能将程序启动起来。

但是,事与愿违,其实在服务器上执行blog start的时候,真正执行的是blog --max_old_space_size=2048start这个参数无法传递下去,因为已经被--max_old_space_size=2048这个flag占用了。这是#!写法的一个特性。

使用hack技巧解决特殊情况

针对上述的特殊情况,曾经有人向Node.js提过一个PR,但是后来发现,不需要Node做任何修改,使用shell的hack技巧就能解决,具体如下:

#!/bin/sh 
":" //# comment; exec /usr/bin/env node --max_old_space_size=2048 "$0" "$@"

const program = require('commander')

program
  .version('0.1.0')
  .command('start')
  .action(function () {
    // start process...
  })

program.parse(process.argv)

...

解释一下:

  • 第一行是指定使用shshell。
  • 第二行其实分为两个部分:":" //# comment;exec /usr/bin/env node --max_old_space_size=2048 "$0" "$@"。前半部分是Bourne Shell内置支持的写法,只需要了解#;之间是可以写注释的就行了;后半部分是使用Node执行相关的命令,$0传递一个参数0,$@传递其他额外的参数。

这样,就能正常传递参数了。

参考资料

http://sambal.org/2014/02/passing-options-node-shebang-line/

1 回复

感谢 @GreyPrinceZote 大佬的无私帮助。

回到顶部