V8与垃圾回收

140 阅读3分钟

node 与 V8

V8本来是浏览器中,引擎使得在非阻塞IO模型,事件驱动下可以在node上面使用,由C++编写,后来被应用在node上面。

node 的内存限制

Node中通过JavaScript使用内存时就会发现只能使用部分内存(64位系统下约为1.4 GB,32位系统下约为0.7 GB)

node 内存分配

在V8中,所有的JavaScript对象都是通过堆来进行分配的 使用 process.memoryUsage()可以看到当前进程的内存分配情况主要有以下几个参数:

{
  rss: 4935680,// 进程分配的物理内存(总分配内存的子集)的大小
  heapTotal: 1826816,//
  heapUsed: 650472,
  external: 49879,//V8 管理的绑定到 Javascript 对象的 C++ 对象的内存使用情况
  arrayBuffers: 9386//代表分配给 ArrayBuffer  SharedArrayBuffer 的内存,包括所有的 Node.js Buffer
}

V8为何要限制堆的大小,表层原因为V8最初为浏览器而设计,不太可能遇到用大量内存的场景。对于网页来说,V8的限制值已经绰绰有余。深层原因是V8的垃圾回收机制的限制 因为垃圾回收机制消耗的时间会阻塞js执行的时间,因此限制了内存的大小是一种方法,至于为什么这样做,跟v8的垃圾回收机制有关。

但Node在启动时可以传递--max-old-space-size或--max-new-space-size来调整内存限制的大小,这种做法也可能导致内存溢出。

V8的垃圾回收机制

采用分代回收,即新生代和老生代 垃圾回收算法中按对象的存活时间将内存的垃圾回收进行不同的分代,然后分别对不同分代的内存施以更高效的算法。

对新生代算法的垃圾回收

新生代指存活时间较短的对象和变量。

新对象回收实现:复制算法和标记整理

步骤如下:

  1. 复制算将semispace 一分为二,使用空间From,空闲空间为To
  2. 将活动对象放在From里面,
  3. 标记整理活跃对象,整理空间
  4. 将活动对象拷贝至To空间
  5. 交换From和To(又称翻转),释放From空间,为下一次空间分配准备。

在拷贝过程中,有可能将对象晋升,即移动到老生代对象里,判断标准主要是两个。

  1. 一轮GC后依然存活
  2. To空间存活率>25%,则集体转移至老生代。 设置25%是因为接下来的to空间将会变成From,如果占比过高,会影响分配

老生代垃圾回收

主要采用以下几种算法

  • 标记清除,清除垃圾空间
  • 标记整理,整理碎片空间

两种对比:新生代是用时间换空间,而老生代反之,因为老生代空间很大,不需要这样做。

增量标记

垃圾回收的3种基本算法都需要将应用逻辑暂停下来,待执行完垃圾回收后再恢复执行应用逻辑,这种行为被称为“全停顿”(stop-the-world)

为了停止这种阻塞时间,V8使用增量标记的方法将垃圾回收的步骤分步执行,使得逻辑和垃圾回收执行交替进行,避免程序的阻塞。

V8后续还引入了延迟清理(lazy sweeping)与增量式整理(incrementalcompaction),让清理与整理动作也变成增量式的