Chrome 与 Node.js 都在使用的 JS 引擎 —— V8 之垃圾回收机制(上篇)

2,540 阅读4分钟

国庆放假,身在厦门的我积极响应防疫政策,遂在家好好学习。新习得关于在 Chrome 浏览器和 Node.js 中使用的 js 引擎 V8 的垃圾回收机制,故而在此分享。篇幅较长,分为上下两篇,本篇主要介绍 V8 的内存分配、大小与新生代垃圾回收算法 scavenge,有不足之处或是任何意见建议,欢迎各位大佬不吝斧正~

我们写的 js 代码要有效果,需要从磁盘中取出,放到内存里,经过 V8 引擎最终编译成机器码,然后由 cpu 执行。内存如果被过多的占用,会引起浏览器卡顿,甚至无响应。所以理解 V8 引擎的内存相关知识,特别是垃圾回收机制(Garbage Collection 简称 GC),是很重要的。
注:在 V8 引擎中是由 Orinoco 模块负责的垃圾回收。

V8 引擎的内存分配

V8 引擎的内存总体上来说分为栈内存堆内存,js 中函数都是在栈中执行,而本篇文章主要介绍的垃圾回收机制,则是发生在堆内存中。堆内存中,分成了好几个空间,其中负责垃圾回收,也是大部分对象存放的空间,其实是 New Space(新生代) 和 Old Space(老生代)这两部分。图示如下:
image.png

下面就开始介绍下新生代和老生代的内容:

定义

  • New Space(新生代)

新生代分为两块大小相等的空间 —— Semi Space From 和 Semi Space To,semi 也就是“半的”意思。

  • Old Space(老生代)

老生代是一个连续的空间,也分为两部分 —— Old Pointer Space 和 Old Data Space。它们的区别在于如果一个对象有被指针引用或是指向其它对象,大部分存放在 Old Pointer Space;如果一对象没有指针引用,则存放在 Old Data Space。注意:所有老生代的对象都是由新生代的对象晋升而来,至于如何晋升,将放在下篇介绍。

内存大小

V8 在 64 位的操作系统中内存为 1.4GB (1464MB),其中新生代空间为 64MB,老生代为 1400MB;在 32 位的操作系统中为 0.7GB(732GB),其中新生代空间为 32MB,老生代为 700MB。注意:Node.js 从 v14 版本开始,内存空间已经扩充到 2GB 了。2GB 的内存空间其实是比较小的,那么为什么要限制为 2GB 的内存大小呢?这里说 2 个原因:

  1. js 在设计之初被认为是非持久化的,用完即可抛弃的,不需要太大的内存空间;
  2. js 是异步单线程的,这意味着运行垃圾回收任务的时候,其它的任务就会停下来,而 V8 一次回收 1.5GB 堆内存的时间大概需要 50ms。如果内存太大,那么可能导致垃圾回收占用的时间就会更长,甚至超过人体可感知的 200ms。

垃圾回收算法

新生代和老生代的垃圾回收机制是不同的,新生代叫做 scavenge 算法,简单来说就是复制。老生代有两种,之前用的是 mark-sweep(标记 - 清除)算法,现在是用的叫做 mark-compact (标记 - 整理)算法。

新生代算法(scavenge)

前面已经说到,新生代空间一分为二成 Semi Space From 和 Semi Space To 两个大小相等的空间。新定义的变量,比如变量 a、b 和 obj,都是首先是放入 Semi Space From 空间(下图 ① ),当开始进行垃圾回收时,那些仍在被使用的变量会被复制一份到 Semi Space To 空间(下图 ②),而被删除的或是失去引用的变量就成了垃圾对象直接被释放(比如变量 a 被删除了)。然后,Semi Space 的 From 和 To 空间互相对调,即新生代互换(下图 ③),新定义的变量就会加入到这个崭新的 From 空间中:

image.png

scavenge 算法有个明显的缺点就是始终有一半的内存空间处于闲置状态而被浪费,但优点就是复制这种形式比较简单,速度快。这属于空间换时间的算法。新生代最多也就 64MB 的空间,那么浪费一半是 32 MB,所以可以接受。而老生代有 1400MB 大小(64 位),浪费 700 MB 空间显然不是明智的,所以老生代有着不同的垃圾回收算法 mark-sweep & mark-compact,我将在下篇中继续介绍~

One More Thing

V8 堆内存剩余部分简介

  • Large Object Space:大对象空间,里面存放的都是超出其它空间大小限制的对象,有专门分配的内存空间。
  • Code Space:代码空间,也就是即时编译器(Just In Time,简称JIT),存储已编译的代码(包含即时编译后的指令),会被执行的代码不是放在这里就是放在 Large Object Space 里。
  • Cell Space、Property Cell Space 和 Map Space:包含相同大小的对象,并对指向对象有一定的限制。

感谢.gif 点赞.png