众所周知Node.js是基于Chrome V8引擎的JavaScript运行时,而我们所探讨的垃圾回收也正是基于V8,你可能觉得奇怪,运行在浏览器环境的JavaScript同样是基于V8引擎,可长久以来,前端似乎都没有需要特别精细控制内存的场景,最多就是在低版本的IE中JS操作DOM而导致的内存泄露问题。之所以在前端开发中不需要格外重视垃圾回收的原因大概是即使由于内存泄露到而导致页面卡顿甚至奔溃,也只是影响终端用户一人(除非代码出现严重bug)而且如果浏览器内存占用过多而导致页面卡顿,用户会去刷新浏览器,或许在这之前还没有出发垃圾回收,所以几乎很少人遇到垃圾回收对应用程序构成性能影响,前端工程师也就没有必要去学习V8的垃圾回收原理。然而当JS转向服务端,而服务端的特点是代码要长时间运行而且要应用海量的并发,基于此Node需要合理高效的使用内存,也就必然需要了解V8的垃圾回收机制。
V8的垃圾回收对大内存的垃圾回收是较为耗时的,而JS又是单线程的,会引起线程的阻塞。由于V8最初是基于浏览器设计的,浏览器端遇到大内存的场景比较少,所以就直接限制了堆内存的大小,这样导致的结果就是在Node中无法直接操作大内存的对象,所以在单个Node进程下,计算机的内存资源无法得充足的使用。当然V8也提供了接口让我们可以自定义内存的大小,但是一旦初始化设置之后就没有办法修改,也就是没有办法进行动态的更改。
V8的垃圾回收机制主要基于分代式垃圾回收机制,分代的意思是利用统计学原理,按对象的存活时间将内存的垃圾回收进行不同的分代,然后分别对不同分代的内存施以更高效的算法,在V8中有两种分代:新生代的内存空间(存活时间较短的对象)、老生代的内存空间(存活时间较长或常驻内存的对象)。
在新生代的内存空间中使用Cheney算法进行垃圾回收,它将堆内存一分为二,每一部分空间称为Semispace。在这两个Semispace空间中,只有一个处于使用中,另一个处于闲置状态。在开始进行垃圾回收时,会检查使用空间中的存活对象,这些存活对象将被复制到另一个空间中,而非存活对象占用的空间将会被释放。完成复制后,两个空间的角色发生对换。因为新生代中存活对象较少所以这样的赋值效率很高。
在老生代对象中存活的对象占比高,所以就不能像Cheney进行赋值,而是采取Mark-Sweep(标记清除)的算法对所有活着的对象进行标记,然后对没有标记的对象进行清除,而老生代对象中存活的对象占绝大多数,所有只清除死亡的对象,性能就会很高。
本篇简要说明了在Node中了解垃圾回收机制的必要性,也简单描述了 V8进行垃圾回收的原理和实现算法。由于V8的垃圾回收特点和JavaScript的单线程情况,垃圾回收在服务器端是众多影响性能的原因之一,需要我们通过精准的内存管理手段对垃圾回收进行控制,尤其是要避免特别耗时的全堆垃圾。