咱们一般理解的 javascript 垃圾回收机制都停留在表面,"会释放不被引用变量内存",最近在读《深刻浅出node.js》的书,详细了解了下 v8 垃圾回收的算法,记录了一些学习笔记。javascript
V8的垃圾回收策略主要基于分代式垃圾回收机制,现代的垃圾回收算法中按对象的存活时间将内存的垃圾回收进行不一样的分代,而后分别对不一样分代的内存施以更高效的算法。在V8中,主要将内存分为新生代和老生代两代。新生代中的对象为存活时间较短的对象, 老生代中的对象为存活时间较长或常驻内存的对象。html
在分代的基础上,新生代中的对象主要经过Scavenge算法进行垃圾回收,在Scavenge的具体 实现中,主要采用了Cheney算法java
Cheney 算法是一种采用复制的方式实现的垃圾回收算法。它将堆内存一分为二,每一部分空间称为 semispace。在这两个 semispace 空间中,只有一个处于使用中,另外一个处于闲置状态。处于使用状态的 semispace 空间称为 From 空间,处于闲置状态的空间称为 To 空间。当咱们分配对象时,先是在 From 空间中进行分配。当开始进行垃圾回收时,会检查 From 空间中的存活对象,这 些存活对象将被复制到 To 空间中,而非存活对象占用的空间将会被释放。完成复制后,From 空 间和To空间的角色发生对换。 简而言之, 在垃圾回收的过程当中, 就是经过将存活对象在两个 semispace 空间之间进行复制。node
Scavenge算法经过牺牲空间换时间的算法很是适合生命周期短的新生代,可是,当一个对象通过屡次复制,生命周期较长的时候或则To空间不足的时候,对象会被分配到进入到老生代中,须要采用新的算法进行垃圾回收。git
Mark-Sweep 并不将内存空间划分为两半,因此不存在浪费一半空间的行为。与 Scavenge 复制活着的对象不一样, Mark-Sweep 在标记阶段遍历堆中的全部对象,并标记活着的对象,在随后的清除阶段中,只清除没有被标记的对象。能够看出,Scavenge 中只复制活着的对象,而 Mark-Sweep 只清理死亡对象。github
Mark-Sweep 在进行一次标记清除回收后,内存空间会出现不连续的状态。这种内存碎片会对后续的内存分配形成问题,由于极可能出现须要分配一个大对象的状况,这时全部的碎片空间都没法完成这次分配,就会提早触发垃圾回收,而此次回收是没必要要的。Mark-Compact 对象在标记为死亡后,在整理的过程当中,将活着的对象往一端移动,移动完成后,直接清理掉边界外的内存算法
为了不出现 JavaScript 应用逻辑与垃圾回收器看到的不一致的状况,垃圾回收的 3 种基本算法都须要将应用逻辑暂停下来,待执行完垃圾回收后再恢复执行应用逻辑,这种行为被称为“全停顿",长时间的"全停顿"垃圾回收会让用户感觉到明显的卡顿,带来体验的影响。以1.5 GB的垃圾回收堆内存为例,V8作一次小的垃圾回收须要50毫秒以上,作一次非增量式的垃圾回收甚至要1秒以上。这是垃圾回收中引发JavaScript线程暂停执行的时间,在 这样的时间花销下,应用的性能和响应能力都会直线降低。性能
为了下降全堆垃圾回收带来的停顿时间,V8先从标记阶段入手,将本来要一口气停顿完成的动做改成增量标记(incremental marking),也就是拆分为许多小“步进”,每作完一“步进” 就让 JavaScript 应用逻辑执行一小会儿,垃圾回收与应用逻辑交替执行直到标记阶段完成学习