logo

鱼肚的博客

Don't Repeat Yourself

动态修改Node.js中的内存限制

背景

Node.js中默认会对可用内存做出限制,当使用的内存超出1.5GB的默认值时,会报内存溢出的异常。

对于这个问题,Node官网中有相关说明:--max-old-space-size

有两种方式可以修改Node的默认内存限制,分别是

1node --max-old-space-size=4096 index.js

1NODE_OPTIONS=--max-old-space-size=4096 node index.js

以webpack为例,大的项目中webpack内存溢出的可能性较大,可以用下面的命令将其可用内存扩大到4GB。

1# 所有平台
2node --max-old-space-size=4096 node_modules/webpack/bin/webpack.js --config webpack.config.js
3
4# 或者*nix
5NODE_OPTIONS=--max-old-space-size=4096 webpack --config webpack.config.js
6
7# Windows
8cross-env NODE_OPTIONS=--max-old-space-size=4096 webpack --config webpack.config.js

相对来说,NODE_OPTIONS的方式使用起来更简单一些,不过它在windows上会需要有cross-env等工具作为前缀。

这两种方式都需要修改启动命令,那么有没有不需要修改启动命令的方式呢?

动态调整

有些命令行工具本身就比较容易出现内存溢出的问题,所以会希望在程序的内部动态调整可用内存,避免使用方每个地方都要重新配置一次。

从上面的NODE_OPTIONS使用方式中可以看出,只需要修改NODE_OPTIONS环境变量,就能实现修改内存限制的目的。那么能不能通过修改process.env.NODE_OPTIONS的方式,实现这个目的呢?

直接修改环境变量

示例代码:

1#!/usr/bin/env node
2process.env.NODE_OPTIONS = process.env.NODE_OPTIONS || ''
3if (!process.env.NODE_OPTIONS.includes('--max-old-space-size=')) {
4  process.env.NODE_OPTIONS = (process.env.NODE_OPTIONS || '') + ` --max-old-space-size=4096`
5}

写一个脚本测试下:

1#!/usr/bin/env node
2const v8 = require('v8')
3process.env.NODE_OPTIONS = process.env.NODE_OPTIONS || ''
4if (!process.env.NODE_OPTIONS.includes('--max-old-space-size=')) {
5  process.env.NODE_OPTIONS = (process.env.NODE_OPTIONS || '') + ` --max-old-space-size=4096`
6}
7
8console.log(Math.round(v8.getHeapStatistics().total_available_size / 1024 / 1024) + ' MB')

调节上面的--max-old-space-size=4096中的4096,换成更小的数字,可以看出console.log的结果是没有区别的,说明这个方法不可行。

猜测是在Node启动的时候,这个参数就已经被读取了,后面再动态修改已经没有作用。

启动子进程

按照上面的猜测,如果先改环境变量,再重新启动脚本,应该就能够生效。

所以我想到了使用子进程的方式,先在主进程中设置好环境变量,然后启动子进程,子进程就会自动继承主进程中的环境变量。

Node.js中有两种启动子进程的方式,spawncluster,此处不详细展开。

使用spawn的话,有可能会需要考虑不同操作系统中的路径问题等边界情况,为了使用方便,这里我采用了cluster的方式。

核心逻辑为:

1#!/usr/bin/env node
2const cluster = require('cluster')
3if (cluster.isMaster) {
4  // 如果是主进程,修改环境变量
5  process.env.NODE_OPTIONS = process.env.NODE_OPTIONS || ''
6  if (!process.env.NODE_OPTIONS.includes('--max-old-space-size=')) {
7    process.env.NODE_OPTIONS = (process.env.NODE_OPTIONS || '') + ` --max-old-space-size=4096`
8  }
9  
10  // 然后启动一个子进程
11  cluster.fork()
12} else {
13  // 如果是子进程,正式加载业务逻辑
14  process.exit(0)
15}

这样我们的测试代码就是:

1#!/usr/bin/env node
2
3const v8 = require('v8')
4const cluster = require('cluster')
5
6process.env.NODE_OPTIONS = process.env.NODE_OPTIONS || ''
7if (!process.env.NODE_OPTIONS.includes('--max-old-space-size=')) {
8  process.env.NODE_OPTIONS = (process.env.NODE_OPTIONS || '') + ` --max-old-space-size=4096`
9}
10
11if (cluster.isMaster) {
12  process.env.NODE_OPTIONS = process.env.NODE_OPTIONS || ''
13  if (!process.env.NODE_OPTIONS.includes('--max-old-space-size=')) {
14    process.env.NODE_OPTIONS = (process.env.NODE_OPTIONS || '') + ` --max-old-space-size=4096`
15  }
16  cluster.fork()
17} else {
18  console.log(Math.round(v8.getHeapStatistics().total_available_size / 1024 / 1024) + ' MB')
19  process.exit(0)
20}
21

试着修改一下代码中的4096,换成其它的值,可以看出console.log的数字发生了变化,说明动态调整内存限制生效了。

总结

可以使用子进程的方式,动态调整Node.js命令行工具的可用内存。先修改NODE_OPTIONS的值,再使用cluster启动新的子进程即可。