您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息

聊聊Node.js中的 GC (垃圾回收)机制

2026/2/3 9:17:10发布15次查看
node 是如何做 gc (垃圾回收)的?下面本篇文章就来带大家了解一下。
gc,garbage collection,垃圾回收。在编程中,一般指的是内存自动回收机制,会定时将不需要用到的数据进行清除。
node.js 底层使用了 v8 引擎。v8 是 google 开源的一款高性能 javascript 引擎,使用了 c++ 进行编写。【相关教程推荐:nodejs视频教程】
node.js 的内存主要分成三部分:
代码空间:存放代码段的地方;
栈:函数调用栈产生的临时变量,为一些基本类型,比如数字、字符串、布尔值,以及对象引用(保存的是地址,不保存对象本身)。
堆:存放对象等数据;
堆内存node.js 底层使用的是 v8,下面讲解一下 v8 的内存回收机制。
首先 js 中所有的对象都会保存在堆内存中。在创建进程的时候,会分配一个初始大小的堆内存,然后我们的对象就会放到里面。
当对象越来越多,堆内存会不够用,此时堆内存会动态地扩大。如果到达一个最大限制(现在通常是 4gb),就会堆内存溢出的错误,然后终止 node.js 进程。
新生代与老生代v8 首先将内存分成两部分,或者说两个生代(generation):
新生代(yong generation):保存一些存活时间较短的对象;
老生代(old generation):保存存活时间长或者长驻的对象。
新生代很小,这里会存放一些存活时间很短的对象,通常它们会被频繁地回收(比如函数的调用栈的一些临时对象)。
新生代可通过 node --max-semi-space-size=size index.js 修改新生代的大小,单位为 mb。
另外,老生代则通过 --max-old-space-size=size 来设置
新生代的 scavenge 算法新生代使用了 scavenge 算法,是一种基于 copy(复制)的算法。
新生代会分成两个空间,这种空间称为 semispace,它们为:
from 空间:新声明的对象会放入这里
to 空间:用作搬移的空间
新声明的对象会放入到 from 空间中,from 空间的对象紧密排布,通过指针,上一对象紧贴下一个对象,是内存连续的,不用担心内存碎片的问题。
所谓内存碎片,指的是空间分配不均匀,产生大量小的连续空间,无法放入一个大对象。
当 from 空间快满了,我们就会遍历找出活跃对象,将它们 copy 到 to 空间。此时 from 空间其实就空了,然后我们将 from 和 to 互换身份。
如果一些对象被 copy 了多次,会被认为存活时间较长,将被移动到老生代中。
这种基于 copy 的算法,优点是可以很好地处理内存碎片的问题,缺点是会浪费一些空间作为搬移的空间位置,此外因为拷贝比较耗费时间,所以不适合分配太大的内存空间,更多是做一种辅助 gc。
mark-sweep 和 mark-compact老生代的空间就比新生代要大得多了,放的是一些存活时间长的对象,用的是 mark-sweep (标记清除)算法。
首先是标记阶段。从根集 root set(执行栈和全局对象)往上找到所有能访问到的对象,给它们标记为活跃对象。
标记完后,就是清除阶段,将没有标记的对象清除,其实就是标记一下这个内存地址为空闲。
这种做法会导致 空闲内存空间碎片化,当我们创建了一个大的连续对象,就会找不到地方放下。这时候,就要用 mark-compact(标记整理)来将碎片的活跃对象做一个整合。
mark-compact 会将所有活跃对象拷贝移动到一端,然后边界的另一边就是一整块的连续可用内存了。
考虑到 mark-sweep 和 mark-compact 花费的时间很长,且会阻塞 javascript 的线程,所以通常我们不会一次性做完,而是用 增量标记 (incremental marking)的方式。也就是做断断续续地标记,小步走,垃圾回收和应用逻辑交替进行。
另外,v8 还做了并行标记和并行清理,提高执行效率。
查看内存相关信息我们可以通过 process.memoryusage 方法拿到内存相关的一些信息。
process.memoryusage();
输出内容为:
{ rss: 35454976, heaptotal: 7127040, heapused: 5287088, external: 958852, arraybuffers: 11314}
说明
rss:常驻内存大小(resident set size),包括代码片段、堆内存、栈等部分。
heaptotal:v8 的堆内存总大小;
heapused:占用的堆内存;
external:v8 之外的的内存大小,指的是 c++ 对象占用的内存,比如 buffer 数据。
arraybuffers:arraybuffer 和 sharedarraybuffer 相关的内存大小,属于 external 的一部分。
以上数字的单位都是字节。
测试最大内存限制写一个脚本,用一个定时器,让一个数组不停地变大,并打印堆内存使用情况,直到内存溢出。
const format = function (bytes) { return (bytes / 1024 / 1024).tofixed(2) + " mb";};const printmemoryusage = function () { const memoryusage = process.memoryusage(); console.log( `heaptotal: ${format(memoryusage.heaptotal)}, heapused: ${format( memoryusage.heapused )}` );};const bigarray = [];setinterval(function () { bigarray.push(new array(20 * 1024 * 1024)); printmemoryusage();}, 500);
需要特别注意的是,不要用 buffer 做测试。
因为 buffer 是 node.js 特有的处理二进制的对象,它不是在 v8 中的实现的,是 node.js 用 c++ 另外实现的,不通过 v8 分配内存,属于堆外内存。
我使用电脑是 macbook pro m1 pro,node.js 版本为 v16.17.0,使用的 v8 版本是 9.4.146.26-node.22(通过 process.versions.v8 得到)。
输出结果为(省略了一些多余的信息):
heaptotal: 164.81 mb, heapused: 163.93 mbheaptotal: 325.83 mb, heapused: 323.79 mbheaptotal: 488.59 mb, heapused: 483.84 mb...heaptotal: 4036.44 mb, heapused: 4003.37 mbheaptotal: 4196.45 mb, heapused: 4163.29 mb<--- last few gcs --->[28033:0x140008000] 17968 ms: mark-sweep 4003.2 (4036.4) -> 4003.1 (4036.4) mb, 2233.8 / 0.0 ms (average mu = 0.565, current mu = 0.310) allocation failure scavenge might not succeed[28033:0x140008000] 19815 ms: mark-sweep 4163.3 (4196.5) -> 4163.1 (4196.5) mb, 1780.3 / 0.0 ms (average mu = 0.413, current mu = 0.036) allocation failure scavenge might not succeed<--- js stacktrace --->fatal error: reached heap limit allocation failed - javascript heap out of memory...
可以看到,是在 4000 mb 之后超出了内存上限,发生堆溢出,然后退出了进程。说明在我的机器上,默认的最大内存为 4g。
实际最大内存和它运行所在的机器有关,如果你的机器的内存大小为 2g,最大内存将设置为 1.5g。
更多node相关知识,请访问:nodejs 教程!
以上就是聊聊node.js中的 gc (垃圾回收)机制的详细内容。
该用户其它信息

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录 Product