为什么需要垃圾回收

JavaScript 和 Java 一样由垃圾回收机制进行自动内存管理,这使得我们开发者不需要像 C/C++ 程序员那样在编写代码的时候时刻关注内存的分配和释放问题。事实上,在浏览器中进行开发时,几乎很少有人能遇到垃圾回收对应用程序性能影响的情况。然而在 Node 环境下,对于性能敏感的服务端程序,内存管理和垃圾回收状况好坏都会对服务产生影响。

Node 与 V8

我们都知道 Google 的 Chrome 浏览器以其性能优异成为焦点,Chrome 背后的成功离不开 JavaScript 引擎 V8。V8 的性能优势使得使用 JavaScript 编写高性能后台服务程序成为可能,在这样的契机下,Node 的创始人 Ryan Dahl 选择了 V8 ,在事件驱动,非阻塞 I/O 模型的设计下实现了 Node。

V8 的内存限制

在一般的后端开发语言中,在基本的内存使用上没有什么限制,然而在 Node 中通过 JavaScript 使用内存就会发现只能使用部分内存,64 位操作系统下约为 1.4GB,32 位系统下约为 0.7GB。这种内存限制,在浏览器环境下使用起来绰绰有余,但是在服务端却限制了开发者随心所欲使用大内存的想法。

垃圾回收机制

内存分代

在 V8 中,主要将内存分为新生代和老生代两种,新生代中的对象为存活时间较短的对象,老生代中的对象为存活时间较长或常驻内存的对象。

新生代算法

新生代中的对象一般存活时间较短,使用 Scavenge GC 算法。
在新生代空间中,内存空间分为两部分,分别为 From 空间和 To 空间。在这两个空间中,必定有一个空间是使用的,另一个空间是空闲的。新分配的对象会被放入 From 空间中,当 From 空间被占满时,新生代 GC 就会启动了。算法会检查 From 空间中存活的对象并复制到 To 空间中,如果有失活的对象就会销毁。当复制完成后将 From 空间和 To 空间互换,这样 GC 就结束了。
当一个对象经过多次复制依然存活时,它将会被认为是生命周期较长的对象,这种对象随后会被移动到老生代中,采用新的算法管理。这个过程称为晋升
晋升的条件主要有两个,一个是对象是否经过 Scavenge 回收,一个是 To 空间的内存占比超过限制。

老生代算法

V8 在老生代中主要采用Mark-SweepMark-Compact相结合方式进行垃圾回收
Mark-Sweep 是标记清除的意思,它分为标记和清除两个阶段。Mark-Sweep 在标记阶段遍历堆中的所有对象,并标记活着的对象,在随后的清除阶段中,只清除没有被标记的对象。
Mark-Sweep 最大的问题是在一次标记清除回收后,内存空间会出现不连续的情况,也就是内存碎片。当出现需要分配一个大对象的情况,所有的碎片空间都无法完成此次分配,就会提前触发垃圾回收,而这是不必要的。
Mark-Compact 是标记整理的意思,它在对象被标记死亡后,在整理的过程中,将活着的对象往一端移动,移动完成后,清理掉边界外的内存。