Node系列 — v8引擎堆内存详解(一)

3,643 阅读2分钟

看完本文你将学到什么

  • v8 堆内存的查看、限制、设置
  • v8 堆内存在不同 Node 版本的表现
  • v8 堆内存参数说明

关于 v8

关于 V8 在浏览器和 Node 中扮演的角色

image.png

v8 的堆内存限制

Node 程序中 javascript 的使用内存是有限制的,注意这个内存是指堆内存,在v8中,所有的 js 对象都存在堆中。在实际应用中不小心触碰到这个边界,进程就会退出。64位系统下为1.4GB,32位系统下为0.7GB。 Node 是基于 v8 引擎构建,所以 v8 会通过自己的方式来进行内存的分配和管理。 那么问题来了:

  • 为什么要对 v8 堆内存进行限制?
  • 为什么内存的大小控制在 1.4GB or 0.7GB ?

v8 的对象分配

内存查询

Node 提供了 process.memoryUsage() 方法,返回描述 Node.js 进程的内存使用情况(以字节 Bytes 为单位)的对象。 执行以下代码可以查看内存的使用情况:

$ node
> process.memoryUsage()
{
  rss: 29196288,
  heapTotal: 5685248,
  heapUsed: 4549120,
  external: 926086,
  arrayBuffers: 10077
}

解释一下这些字段:

      rss: 常驻集大小, 是为进程分配的物理内存(总分配内存的子集)的大小,包括所有的 C++ 和 JavaScript 对象与代码。

      heapTotal: v8 已申请的堆内存大小。

      heapUsed: 当前堆内存已使用的大小。

      external: 代表 V8 管理的绑定到 Javascript 对象的 C++ 对象的内存使用情况。

      arrayBuffers: 代表分配给 ArrayBuffer 和 SharedArrayBuffer 的内存,包括所有的 Node.js Buffer。

在实际业务中,我们声明的每一个变量对象都被分配在堆中,如果已申请的堆中空闲内存不够分配新的对象,将继续申请堆内存,直到超出 v8 的限制。

那为什么 v8 要限制堆的大小呢? 🤔️

原因是 v8 的垃圾回收机制的限制。在 Node 中,垃圾回收是 v8 自动执行的,而 Node 又是单线程,所以在回收的过程中 js 线程会阻塞。以1.5G的垃圾回收堆内存为例,v8做一次小的垃圾回收需要 50ms 以上。一次非增量式的垃圾回收甚至需要一秒以上,这个时间损耗会严重影响程序的性能和响应能力。因此,出于性能考虑,v8 的堆内存限制和其大小的默认临界值也就可以理解了。

内存上限默认值

在《深入浅出Nodejs》中说明,64位系统约为1.4GB,32位系统约为0.7GB。
我根据本地几个 node 版本亲自测试了一下: 创建 memorySize.js:

const v8 = require('v8')
console.log('HeapStatistics:',v8.getHeapStatistics()) // 查询堆内存上限设置

在 node 10.12.0 版本中,打印如下:

HeapStatistics: { total_heap_size: 6537216,
  total_heap_size_executable: 1048576,
  total_physical_size: 5230576,
  total_available_size: 1520889432,
  used_heap_size: 3865464,
  heap_size_limit: 1526909922, // 1.42G
  malloced_memory: 8192,
  peak_malloced_memory: 416320,
  does_zap_garbage: 0 }

在 node 12.14.1 版本中,打印如下:

HeapStatistics: {
  total_heap_size: 4472832,
  total_heap_size_executable: 524288,
  total_physical_size: 3317184,
  total_available_size: 2194939360,
  used_heap_size: 2277872,
  heap_size_limit: 2197815296, // 2.04G
  malloced_memory: 8192,
  peak_malloced_memory: 127176,
  does_zap_garbage: 0,
  number_of_native_contexts: 1,
  number_of_detached_contexts: 0
}

在 node 15.14.0 版本中,打印如下

HeapStatistics: {
  total_heap_size: 4284416,
  total_heap_size_executable: 524288,
  total_physical_size: 3122176,
  total_available_size: 4342823264,
  used_heap_size: 3384776,
  heap_size_limit: 4345298944, // 4.04G
  malloced_memory: 254120,
  peak_malloced_memory: 90736,
  does_zap_garbage: 0,
  number_of_native_contexts: 1,
  number_of_detached_contexts: 0
}

也就是说,随着 Node 的发展,v8堆内存在不同版本默认设置是不一样的。

修改堆内存默认值

v8 的默认堆内存上限是可以修改的(两种方法):

  1. Node 启动时可以传递 --max-old-space-size--max-new-space-size 参数。
  2. 如果 Node8.x及以上的版本,还可以通过 NODE_OPTIONS 这个系统环境变量来配置 export NODE_OPTIONS=--max_old_space_size=2048

这里说明一下 --max-old-space-size--max-new-space-size 两个参数:

  • --max-old-space-size: 设置堆内存老生代空间;
  • --max-old-space-size: 设置堆内存新生代空间;(后面在内存分配和垃圾回收环节给大家详细解释) 上述参数在 v8 初始化时生效,一旦生效就不能动态改变了。

演示一下: 创建 memorySize.js:

const v8 = require('v8')
console.log('memoryUsage:',process.memoryUsage())
console.log('HeapStatistics:',v8.getHeapStatistics()) // 查询堆内存上限设置

启动 node ,设置堆内存大小上限为 5000MB:

$ node
$ node --max-old-space-size=5000 memorySize.js

打印如下:

image.png

v8.getHeapStatistics() 结果中的 heap_size_limit 5365510286 字节,即 5116 MB。


因为Node本身处于快速发展阶段,很多资料都与当前 Node 的表现存在着部分差异,为了严谨性,查询很多资料,肝了一夜!各位看官了解的可以留言沟通,不了解的看完之后点个赞吧😭。

总结:

关于堆内存还有很多相关知识,本文主要介绍关于 v8 堆内存的查看、设置,及其在 Node 不同版本的表现。中间涉及的其它参数大家可以自行了解一下。欢迎大家一起交流。下一节为大家介绍内存的具体分配及回收模式。

参考资料:《深入浅出Nodejs》,社区实践