前言

Node是以V8作为引擎进行开发,所以内存控制也要考虑V8的因素,V8中对内存进行了限制,主要原因在于在浏览器运行并不需要这么大的内存分配,每开一个标签页就是一个V8实例,限制的内存远远大于所需内存了,另外一点就是在进行垃圾回收时也是非常耗时的,如果内存较多,那么占用JavaScript线程的时间就会相对变长,所以限制V8的内存使用是合理的。但是在服务端,我们仍然会存在一些大内存操作的时候,使用process.memoryUsage()可以查看内存使用情况。在一些极端情况下可以设置分配更多的内存空间。

新生代和老生代

Node内存管理采用分代管理的机制,将内存分为新生代和老生代两种,新生代对象就是那些存活时间短的对象,老生代对象就是存活时间长的对象。在进行垃圾回收时,并没有一种算法能够针对不同情况达到最好的回收机制。所以Node采用分代处理的机制管理内存。在Node中为新生代对象分配的单个区域(reserve_semispace_size)内存空间为16MB(64位)和8MB(32位),新生代管理需要两个区域进行管理,所以为32MB(64位)和16MB(32位)。为老生代分配的内存空间(max_old_generator_size)为1400MB(64位)和700MB(32位)。所以能够使用的总空间为
4 * reserve_semispace_size + max_old_generator_size = 1464MB(64位),大约为1.4G,32位同理

为什么是4倍的reserve_semispace_size不太了解,按照意思应该是2倍的

垃圾回收机制

scavenge算法

新生代对象采用scavenge算法进行垃圾回收,主要原理在于对新生代内存空间平分为两份(reserve_semispace_size),一份为From区域,一份为To区域,对象从From区域向To区域做复制操作,存活对象会复制成功,死对象不会进行复制操作,最后删除那些死对象。操作完毕过后将From和To对调,便于进行下一次复制操作。scavenge算法适用于新生代对象的优势在于,新生代内存空间中存活对象相对较少,复制操作较少。

判断新生代对象与老生代对象的方法在于是否经历过scavenge算法,新生代对象如果经历过scavenge算法后没有被清除,则将其转化为老生代对象

标记清除

老生代对象内存空间采用标记请求的方式进行垃圾回收,垃圾回收器运行时会将活对象打上标记,最后清除那些没有标记的对象。标记清除适用于老生代内存空间的原因在于老生代对象中死对象较少。标记清除的缺陷在于内存碎片较多,因为按顺序分配的内存空间可能中间一些零碎空间被清除,这些空间不能得以使用,另外一种算法(Mark-Compat)就是在每次标记清除后对内存空间进行平移,充分利用零碎空间。但是Node在考虑性能原因采用标记清除的方式,在有大对象、零碎空间不能得以利用时会使用Mark-Compat算法

增量标记

在运行垃圾回收机制时为了避免JavaScript运行逻辑与处理垃圾时不一致的情况,所以在进行垃圾回收时会将应用逻辑停下来。在新生代处理中由于活对象较少的原因基本不占用时间,所以不会有什么影响。但是老生代内存空间中活对象多,本身内存空间大,存活对象的标记、整理、死对象的清除等都需要大量的时间,所以Node采用增量标记的方式进行优化,将标记变为一步一步进行,标记一会,然后将处理交给应用逻辑,然后在进行标记