代码改变世界

nodejs v8 内存溢出问题

2021-06-12 23:27  youxin  阅读(2521)  评论(0编辑  收藏  举报

 

nodejs:

https://nodejs.org/dist/latest-v14.x/docs/api/cli.html#cli_max_old_space_size_size_in_megabytes

Useful V8 options#

V8 has its own set of CLI options. Any V8 CLI option that is provided to node will be passed on to V8 to handle. V8's options have no stability guarantee. The V8 team themselves don't consider them to be part of their formal API, and reserve the right to change them at any time. Likewise, they are not covered by the Node.js stability guarantees. Many of the V8 options are of interest only to V8 developers. Despite this, there is a small set of V8 options that are widely applicable to Node.js, and they are documented here:

--max-old-space-size=SIZE (in megabytes)#

Sets the max memory size of V8's old memory section. As memory consumption approaches the limit, V8 will spend more time on garbage collection in an effort to free unused memory.

On a machine with 2GB of memory, consider setting this to 1536 (1.5GB) to leave some memory for other uses and avoid swapping.

$ node --max-old-space-size=1536 index.js

旧空间"是V8托管(也称为垃圾收集)堆的最大且最可配置的部分(即JavaScript对象所在的位置),并且--max-old-space-size标志控制其最大大小.随着内存消耗接近极限,V8将花费更多时间进行垃圾收集,以释放未使用的内存.

如果堆内存消耗(即GC无法释放的活动对象)超出限制,V8将使您的进程崩溃(缺少替代方案),因此您不希望将其设置得太低.当然,如果你设置得太高,那么V8允许的额外堆使用可能会导致整个系统内存不足(并且交换或杀死随机进程,因为缺少备选方案).

Node对内存泄露十分敏感,一旦线上应用有成千上万的流量,那怕是一个字节的内存泄漏也会造成堆积,垃圾回收过程中将会耗费更多时间进行对象扫描,应用响应缓慢,直到进程内存溢出,应用奔溃。

 

内存的限制

Node中并不像其他后端语言中,对内存的使用没有多少限制。在Node中使用内存,只能使用到系统的一部分内存,64位系统下约为1.4GB,32位系统下约为0.7GB。这归咎于Node使用了本来运行在浏览器的V8引擎。

V8引擎的设计之初只是运行在浏览器中,而在浏览器的一般应用场景下使用起来绰绰有余,足以胜任前端页面中的所有需求。

虽然服务端操作大内存也不是常见的需求,但是万一有这样的需求,还是可以解除限制的。
在启动node程序的时候,可以传递两个参数来调整内存限制的大小。

node --max-nex-space-size=1024 app.js // 单位为KB
node --max-old-space-size=2000 app.js // 单位为MB

这两条命令分别对应Node内存堆中的「新生代」和「老生代」



链接:https://www.jianshu.com/p/74a466789ff4

 

Node.js的8.0版本以上可以这样调整

export NODE_OPTIONS=--max_old_space_size=4096

也可以使用自动化、工程化配置插件

increase-memory-limit

https://gist.github.com/tjunghans/90ff3bbf575b8b1da41f3fb56e374931

Command line

node --help --v8-options | grep -B 1 -A 1 max-old-space
        type: bool  default: false
  --max-old-space-size (max size of the old space (in Mbytes))
        type: size_t  default: 0

"Old space" is the biggest and most configurable section of V8's managed (aka garbage-collected) heap (i.e. where the JavaScript objects live), and the --max-old-space-size flag controls its maximum size. As memory consumption approaches the limit, V8 will spend more time on garbage collection in an effort to free unused memory. If heap memory consumption (i.e. live objects that the GC cannot free) exceeds the limit, V8 will crash your process (for lack of alternative), so you don't want to set it too low. Of course, if you set it too high, then the additional heap usage that V8 will allow might cause your overall system to run out of memory (and either swap or kill random processes, for lack of alternative). In summary, on a machine with 2GB of memory I would probably set --max-old-space-size to about 1.5GB to leave some memory for other uses and avoid swapping.

Setting

export NODE_OPTIONS=--max-old-space-size=8192

References


nodejs内存使用:https://devcenter.heroku.com/articles/node-memory-use


https://grizzlybit.info/blog/increase-nodejs-memory-limit

In Node.js, we don't explicitly manage the memory, we rather leave it to v8 garbage collector to do that for us. But it might not have the wiggle room to crunch the data we want it to. Hence, we might end up with a FATAL ERROR like below:

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
 1: 0x1011d1c65 node::Abort() (.cold.1) [/usr/local/bin/node]
 2: 0x10009f919 node::Abort() [/usr/local/bin/node]
 3: 0x10009fa7f node::OnFatalError(char const*, char const*) [/usr/local/bin/node]
 4: 0x1001e3867 v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [/usr/local/bin/node]
 5: 0x1001e3807 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/usr/local/bin/node]
 6: 0x10036b995 v8::internal::Heap::FatalProcessOutOfMemory(char const*) [/usr/local/bin/node]
 7: 0x100373a3c v8::internal::Heap::AllocateRawWithRetryOrFail(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/usr/local/bin/node]
 8: 0x10033fdbd v8::internal::Factory::NewFixedArrayWithFiller(v8::internal::RootIndex, int, v8::internal::Object, v8::internal::AllocationType) [/usr/local/bin/node]
 9: 0x1004b9d29 v8::internal::(anonymous namespace)::ElementsAccessorBase<v8::internal::(anonymous namespace)::FastHoleySmiElementsAccessor, v8::internal::(anonymous namespace)::ElementsKindTraits<(v8::internal::ElementsKind)1> >::GrowCapacityAndConvertImpl(v8::internal::Handle<v8::internal::JSObject>, unsigned int) [/usr/local/bin/node]
10: 0x1004b98e8 v8::internal::(anonymous namespace)::ElementsAccessorBase<v8::internal::(anonymous namespace)::FastHoleySmiElementsAccessor, v8::internal::(anonymous namespace)::ElementsKindTraits<(v8::internal::ElementsKind)1> >::SetLengthImpl(v8::internal::Isolate*, v8::internal::Handle<v8::internal::JSArray>, unsigned int, v8::internal::Handle<v8::internal::FixedArrayBase>) [/usr/local/bin/node]
11: 0x100582d51 v8::internal::JSArray::SetLength(v8::internal::Handle<v8::internal::JSArray>, unsigned int) [/usr/local/bin/node]
12: 0x10024c9a6 v8::internal::Accessors::ArrayLengthSetter(v8::Local<v8::Name>, v8::Local<v8::Value>, v8::PropertyCallbackInfo<v8::Boolean> const&) [/usr/local/bin/node]
13: 0x100418466 v8::internal::PropertyCallbackArguments::CallAccessorSetter(v8::internal::Handle<v8::internal::AccessorInfo>, v8::internal::Handle<v8::internal::Name>, v8::internal::Handle<v8::internal::Object>) [/usr/local/bin/node]
14: 0x10041503c v8::internal::Runtime_StoreCallbackProperty(int, unsigned long*, v8::internal::Isolate*) [/usr/local/bin/node]
15: 0x1009dcb79 Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit [/usr/local/bin/node]
16: 0x39249b3c82ac
17: 0x100962524 Builtins_InterpreterEntryTrampoline [/usr/local/bin/node]
[1]    39205 abort      node index.js

我电脑运行得到的日志:
{ rss: 1524232192,
  heapTotal: 1509670912,
  heapUsed: 1456969344,
  external: 8272 }

<--- Last few GCs --->
n [13831:0x2719980]    19972 ms: Mark-sweep 1389.5 (1439.7) -> 1389.4 (1439.7) MB, 138.2 / 0.0 ms  (+ 0.0 ms in 0 steps since start of marking, biggest step 0.0 ms, walltime since start of marking 138 ms) (average mu = 0.102, current mu = 0.006) allocation [13831:0x2719980]    20112 ms: Mark-sweep 1389.4 (1439.7) -> 1389.4 (1439.7) MB, 139.3 / 0.0 ms  (+ 0.0 ms in 0 steps since start of marking, biggest step 0.0 ms, walltime since start of marking 140 ms) (average mu = 0.055, current mu = 0.003) allocation 

<--- JS stacktrace --->

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

    0: ExitFrame [pc: 0x29dbf095be1d]
Security context: 0x01b54756a5d1 <JSObject>
    1: /* anonymous */ [0xa9e06484c29] [/server/mytest/nodeJsMemoryTest.js:~1] [pc=0x29dbf09fb3c3](this=0x0a9e06484d59 <Object map = 0x3f6dd8882571>,exports=0x0a9e06484d59 <Object map = 0x3f6dd8882571>,require=0x0a9e06484d19 <JSFunction require (sfi = 0xbf6fdec6301)>,module=0x0a9e06484c91 <Module map = 0x3f6dd88d4fd9>,__filename=0x0bf6fdecd261 <String[34]: /...

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
 1: 0x8fb090 node::Abort() [node]
 2: 0x8fb0dc  [node]
 3: 0xb0322e v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [node]
 4: 0xb03464 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [node]
 5: 0xef74c2  [node]
 6: 0xef75c8 v8::internal::Heap::CheckIneffectiveMarkCompact(unsigned long, double) [node]
 7: 0xf036a2 v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [node]
 8: 0xf03fd4 v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [node]
 9: 0xf06c41 v8::internal::Heap::AllocateRawWithRetryOrFail(int, v8::internal::AllocationSpace, v8::internal::AllocationAlignment) [node]
10: 0xecfd36 v8::internal::Factory::AllocateRawArray(int, v8::internal::PretenureFlag) [node]
11: 0xed05ba v8::internal::Factory::NewFixedArrayWithFiller(v8::internal::Heap::RootListIndex, int, v8::internal::Object*, v8::internal::PretenureFlag) [node]
12: 0xed0b77 v8::internal::Factory::NewUninitializedFixedArray(int, v8::internal::PretenureFlag) [node]
13: 0xe91a60  [node]
14: 0xe94bf2  [node]
15: 0xe94f52  [node]
16: 0xaf8ec6 v8::internal::Accessors::ArrayLengthSetter(v8::Local<v8::Name>, v8::Local<v8::Value>, v8::PropertyCallbackInfo<v8::Boolean> const&) [node]
17: 0xf6bd57 v8::internal::Runtime_StoreCallbackProperty(int, v8::internal::Object**, v8::internal::Isolate*) [node]
18: 0x29dbf095be1d 
Aborted

 

If you would like to trigger this error, we can run a simple program like:

//index.js

const references = [];

const allocateSize = (size) => {
  const numbers = size / 8;
  const arr = [];
  arr.length = numbers;
  for (let i = 0; i < numbers; i++) {
    arr[i] = i;
  }
  return arr;
};

while (true) {
  //steps of 100
  const allocation = allocateSize(100 * 1024);
  //keep in memory so it is not garbage collected
  references.push(allocation);
  const usage = process.memoryUsage();
  console.log(usage);
}
或者:
const array = [];
while (true) {
  // This makes the array bigger on each iteration
  array.push(new Array(10000000));

  const memory = process.memoryUsage();
  console.log((memory.heapUsed / 1024 / 1024 / 1024).toFixed(4), 'GB');
}

 

 

The loop above should run and exhaust the memory limit for your Node.js program.

process.memoryUsage() is used to determine the help size for your proces

How to Work-Around the Memory Limit?

V8 engine comes with a parameter to work around and raise the limit of the heap size through the use of --max-old-space-size , hence to increase your heap memory limit to 2 GB you would do:

node --max-old-space-size=2048 index.js

This will give you the memory to burn for your expensive usage/calculations for your Node.js process.

You can even go higher for your memory limit:

node --max-old-space-size=1024 app.js                     # increase to 1gb
node --max-old-space-size=2048 app.js                     # increase to 2gb
node --max-old-space-size=3072 app.js                     # increase to 3gb
node --max-old-space-size=4096 app.js                     # increase to 4gb
node --max-old-space-size=5120 app.js                     # increase to 5gb
node --max-old-space-size=6144 app.js                     # increase to 6gb

Now you would wonder, how far can I push this limit? 🤔

Theoretically, in a 64-bit system, you can have a heap size of 16 TB of memory, but obviously, you won't be able to each anywhere as close to that. It will be limited by the physical memory limit on the machine and then it will try and leverage the hard disk of the machine to swap data.

How to Increase Memory When You're Using Pm2?

PM2 is a daemon process manager that will help you manage and keep your application online 24/7, which is essential for running applications in a production environment. Sometimes, you want to demonize your application using PM2 but also you would like to allocate a higher memory limit for your application.

To do that PM2 lets you to pass in Node.js arguments when forking a process through the --node-args flag, which can be leveraged to pass in a --max-old-space-size value for your forked process

pm2 start index.js --node-args="--max-old-space-size=1024" # increase to 1gb
pm2 start index.js --node-args="--max-old-space-size=2048" # increase to 2gb
pm2 start index.js --node-args="--max-old-space-size=3072" # increase to 3gb
pm2 start index.js --node-args="--max-old-space-size=4096" # increase to 4gb
pm2 start index.js --node-args="--max-old-space-size=5120" # increase to 5gb
pm2 start index.js --node-args="--max-old-space-size=6144" # increase to 6gb

Boom! your application has a lot of room to wiggle around now, and also it is backed up by PM2 a as process monitor for a more robust high-performance production development.

You can see further helpful v8 options and flag by running the command:

node --help --v8-options

 

nodejs诊断内存泄露问题:debugging-a-memory-leak

https://devcenter.heroku.com/articles/node-memory-use

The memory limits of different Node.js versions

There are no official numbers regarding those limits, but with a small program I wrote, I was able to get them for different Node.js versions on a 64bit architecture.

Node Version Limit
15.0.1 4.03 GB
14.15.0 4.03 GB
13.14.0 2.01 GB
12.19.0 2.01 GB
11.15.0 1.34 GB
10.15.3 1.34 GB
9.11.2 1.35 GB

As we can see in this table, the default limits increased with some versions. 4 GB of heap-memory should usually be enough for most use-cases.

You can test this yourself by creating an index.js file with the following code and running it with the Node.js version you want to know the limit for.