内存管理简介
来自于MDN的简介:
像C语言这样的底层语言一般都有底层的内存管理接口,比如 malloc()和free()。相反,JavaScript是在创建变量(对象,字符串等)时自动进行了分配内存,并且在不使用它们时“自动”释放。 释放的过程称为垃圾回收。这个“自动”是混乱的根源,并让JavaScript(和其他高级语言)开发者错误的感觉他们可以不关心内存管理。
内存的生命周期
内存的生命周期基本如下:
- 分配需要的内存
- 时候用分配到的内存进行读写操作
- 不需要是将该内存归还或释放
v8的内存模型
一个运行中的程序总是与内存中的一部分空间相对应,这部分空间被称为Resident Set(驻留集)。
其内存模型如下:

图中各名词的解释:
- Resident Segment 驻留集(当前程序所占用的全部空间)
- Code Segment 存放正在执行的代码
- Stack 栈 存放基本类型变量以及对象的指针(因为栈中的内存较小,栈中的每一块内存大小固定)
- Heap 堆 存放复杂引用类型
- Used Heap 堆中已经使用的内存量
Node中的内存查看方式
Node中可以使用process.memoryUsage()方法来查看当前进程的内存使用情况
因为该方法输出的内存单位为字节,我们在下面的方法中将其封装从而输出以M为单位
1 |
function (bytes) { |
其中,process.memoryUsage方法返回的对象中的所有属性说明如下:
- rss(resident set size): 当前进程占用的内存部分,包括之前提到过的代码本身、存储基本类型变量和对象指针的栈、存储复杂引用类型的堆
- heapTotal: 堆中总共申请到的内存量
- heapUsed: 堆中目前已经使用的内存量
- external: v8引擎内部的C++对象占用的内存
我们知道,Js中的所有复杂引用类型都是存储在堆中的。因此,当我们创建一个对象时,该对象所占用的内存就会被存放在堆中。如果,当前堆的空闲内存大小已经不够再分配一个新的对象,那么将会继续申请堆内存,直到堆的大小超过V8的限制为止。
在这里提一句,在默认情况下,v8堆内存的最大值在64位系统下大约为1.4G,在32位系统下大约为0.7G
v8的垃圾回收机制
垃圾回收是指回收那些在应用程序中不再被引用的对象
例如,我们将一个对象指向null后该对象就会垃圾回收机制自动回收掉
v8的垃圾回收机制主要基于分代式垃圾回收机制
新生代与老生代
在V8中,主要将内存分为新生代和老生代两代。新生代中的对象为存活时间较短的对象,老生代中的对象为存活时间较长或常驻内存中的对象

由此,我们可以看到v8申请到堆的大小就是新生代所用内存空间加上老生代所用内存空间
新生代内存空间所使用的的垃圾回收算法(Scavenge算法)
在新生代内存空间中,v8主要使用Scavenge算法来进行垃圾回收。
该算法是一种采用复制的方式来实现的垃圾回收算法。
它会将新生代内存一份为二,其中一个空间称为From空间,该空间是当前正在使用的空间;另一个空间成为To空间,该空间是当前正在被闲置的空间(该空间中的内存没有被任何对象使用)

当开始进行垃圾回收时,v8会检查From空间是否还有存活的对象,如果有,那么将这些对象复制到当前空闲的To空间;而,其余非存活的对象则会被回收,它们所占用的空间也会被释放。完成复制后,From空间和To空间的角色将会被互换,也就是刚刚的From空间在复制后就会变为空闲的To空间;而刚刚空闲的To空间在复制后则变成From空间

该算法的缺点是,将新生代内存空间一分为二后,新生代中的内存使用变为原来的一半。
但是,其有一个显著的优点。该算法只复制存活的对象,并且存活时间短的存活对象只占所有新生代中对象的极少一部分,因此,复制效率就会极其高
这便是典型的以空间换时间策略的算法。
另外,如果一个新生代中的对象经过多次SC垃圾回收算法的复制仍然存在,那么v8将会认为该对象已经是存活时间较长的对象了,它已经不适合再呆在新生代内存空间中了,更加确切的说,该对象已经不再适合用SC算法来进行管理了。那么,v8就会将该对象移动到老生代内存空间中,采用新的垃圾回收算法进行管理
老生代内存空间所使用的垃圾回收算法
由于老生代内存空间中的对象生存事件较长。因此,如果仍旧采用SC算法就会有明显的两个缺点:
- 因为对象存活时间长,因此当该算法运行时,存活对象所占的比例极高,那么复制对象的效率就会明显很低
- 另外,就是要有一半的内存空间是空闲出来的,这样就会造成浪费
因此,v8中采用Mark-Sweep(标记清除)&Mark-Compact(标记整理)
标记清除
标记清除算法分为标记和清除两个阶段。
- 标记阶段 遍历堆中的所有对象,将存活的对象进行标记
- 清除阶段 清除没有进行标记的对象
可以看出,该算法只清理已经不再存活的对象。由于,在老生代内存空间中,死对象所占的比例较少,因此,清除死对象的方式会极其高效

我们可以看到,该算法将死对象清除后会造成内存空间的不连续,这也将会对后续的内存分配造成问题,因为,很可能后续会有一个需要较大的内存空间的对象要被分配,但是所有的碎片空间都无法满足此次分配,因此,就会提前触发垃圾回收机制并将该大内存空间的对象回收
为了解决这个问题,标记整理的算法就被提了出来
标记整理
标记整理算法是标记清除算法的改进版。
标记的过程还是一样的,区别在于清除过程,标记清除算法只是直接回收死对象的内存空间;
而标记整理算法是在整理过程中,将活着的对象往一端移动,那么当移动完成后,另一端就都是死的对象,因此,此时只要直接清除掉那一整块死掉的对象内存空间即可


v8对于标记清除和标记整理算法的考虑
由于在标记整理中,v8需要将对象移动,因此它的执行速度就会相对比标记清除算法要慢。
因此,v8对于这两个算法是结合使用的。v8主要使用标记清除算法,只有当老生代内存空间不足以应对即将要分配的占用大内存空间对象的时候才会使用标记整理算法




近期评论