前言
Chrome V8垃圾回收机制学习记录。
背景
对于前端和Node.js服务端开发的同学来说,关于垃圾回收、内存释放这块不需要像C/C++的同学那样在创建一个对象之后还需要手动创建一个delete/free的这样一个操作进行GC(垃圾回收), nodejs和java一样了, 由虚拟机进行内存自动管理,但是这样并不保证就可以高枕无忧。
在实际开发中肯能由于使用不当导致的内存泄漏也是一个超级严重的问题,所以作为一名合格的工程师,还是有必要学习理解v8虚拟机的内存是怎么样管理的。
node.js中的GC
nodejs是一个基于chrome V8的javascript运行环境,这是node官网的第一句话。我们讲的node.js的垃圾回收机制实际上就是v8的GC。
V8垃圾回收机制
垃圾回收是回收那些在应用程序中不在引用的对象,当一个对象无法从根节点访问这个对象就会作为垃圾回收的候选对象。这里的根对象可以为全局对象、局部变量,无法从根节点访问值的也就是不会再被任何其他活动对象所引用。
V8内存大小限制
内存在服务端本来就是一个寸土寸金的东西。
在 V8 中限制 64 位的机器大约 1.4GB,32 位机器大约为 0.7GB。因此,对于一些大内存的操作需谨慎否则超出 V8 内存限制将会造成进程退出。
如果想要调大内存可以使用如下参数来扩展老生代的内存
node --max_old_space_size=4096 xxx
单位是MB,比如上述例子是将最大可用内存扩展至4G。
新生代和老生代
绝对大多数的应用程序对象的存活周期都会很短,而少数对象的存活周期将会很长为了利用这种情况,V8 将堆分为两类新生代和老生代,新空间中的对象都非常小大约为 1-8MB,这里的垃圾回收也很快。新生代空间中垃圾回收过程中幸存下来的对象会被提升到老生代空间。
新生代空间
由于新空间中的垃圾回收很频繁,因此它的处理方式必须非常的快,采用的 Scavenge 算法,该算法由 C.J. Cheney 在 1970 年在论文 A nonrecursive list compacting algorithm 提出。
Scavenge算法
Scavenge 是一种复制算法,新生代空间会被一分为二划分成两个相等大小的 from-space 和 to-space。它的工作方式是将 from space 中存活的对象复制出来,然后移动它们到 to space 中或者被提升到老生代空间中,对于 from space 中没有存活的对象将会被释放。完成这些复制后在将 from space 和 to space 进行互换。
Scavenge 算法非常快适合少量内存的垃圾回收,但是它有很大的空间开销,对于新生代少量内存是可以接受的。
老生代空间
新生代空间在垃圾回收满足一定条件(是否经历过 Scavenge 回收、to space 的内存占比)会被晋升到老生代空间中,在老生代空间中的对象都已经至少经历过一次或者多次的回收所以它们的存活概率会更大。在使用 Scavenge 算法则会有两大缺点一是将会重复的复制存活对象使得效率低下,二是对于空间资源的浪费,所以在老生代空间中采用了 Mark-Sweep(标记清除) 和 Mark-Compact(标记整理) 算法。
Mark-Sweep
Mark-Sweep 处理时分为标记、清除两个步骤,与 Scavenge 算法只复制活对象相反的是在老生代空间中由于活对象占多数 Mark-Sweep 在标记阶段遍历堆中的所有对象仅标记活对象把未标记的死对象清除,这时一次标记清除就已经完成了。
看似一切 perfect 但是还遗留一个问题,被清除的对象遍布于各内存地址,产生很多内存碎片。
Mark-Compact
在老生代空间中为了解决 Mark-Sweep 算法的内存碎片问题,引入了 Mark-Compact(标记整理算法),其在工作过程中将活着的对象往一端移动,这时内存空间是紧凑的,移动完成之后,直接清理边界之外的内存。
\
V8垃圾回收总结
为何垃圾回收是昂贵的?V8 使用了不同的垃圾回收算法 Scavenge、Mark-Sweep、Mark-Compact。这三种垃圾回收算法都避免不了在进行垃圾回收时需要将应用程序暂停,待垃圾回收完成之后在恢复应用逻辑,对于新生代空间来说由于很快所以影响不大,但是对于老生代空间由于存活对象较多,停顿还是会造成影响的,因此,V8 又新增加了增量标记的方式减少停顿时间。
关于 V8 垃圾回收这块笔者讲的很浅只是自己在学习过程中做的总结,如果你想了解更多原理,深入浅出 Node.js 这本书是一个不错的选择,还可参考这这几篇文章