JavaScript 内存管理
内存的生命周期
不管以什么样的方式来管理内存,内存的管理都会有如下的生命周期:
- 分配申请你需要的内存(申请)
- 使用分配的内存(存放一些东西,比如对象等)
- 不需要使用时,对其进行释放。
内存的分配
- JS 在定义变量的时候为我们分配内存。
- JS 对于基本数据类型内存的分配会在执行时,直接在 栈空间进行分配。
- JS 对于复杂数据类型内存的分配会在 堆内存中开辟一块空间,并且这块空间的指针返回变量引用。
垃圾回收器 Garbage Collection,简称 GC
- 因为内存的大小是有限的,所以当内存不再需要的时候,我们需要对其进行释放,以便腾出更多的内存空间。
- 对于那些不再使用的对象,我们都称之为是垃圾,它需要被回收,以释放更多的内存空间。
常见的 GC 算法
一、引用计数
var obj = { name: "hello" };
var info = { name: "tom", friend: obj };
var p = { name: "jerry", friend: obj };
info 中的 friend 指向 obj
p 中的 friend 指向 obj
有人指向它,就会把当前 计数器 + 1;
当引用计数变成 0 的时候,
GC
就会回收它引用计数存在一个很大的弊端:循环引用
var obj1 = { friend: obj2 };
var obj2 = { friend: obj1 };
obj1.friend = null;
如果忘记了,就产生循环引用
二、标记清除
这个算法是设置一个根对象(root object),垃圾回收器会定期从这个根开始,找所有从根开始有引用的对象,对用那些没有引用到的对象,就认为是不可用的对象,就会清除。
这个算法可以很好的解决循环引用的问题
目前 JS 引擎主要采用 标记算法和其它的算法
垃圾回收是怎么实现的?
第一步,通过 GC Root 标记空间中活动对象和非活动对象。
目前 V8 采用的可访问性算法来判断堆中的对象是否是活动对象。具体地讲,这个算法是将一些 GC Root 作为初始存活的对象的集合,从 GC Roots 对象出发,遍历 GC Root 中的所有对象;
- 通过 GC Root 遍历到的对象,我们就认为该对象是可访问的,那么必须保证这些对象应该在内存中保留,我们也称可访问的对象为活动对象。
- 通过 GC Root 没有遍历到的对象,则是不可访问的,那么这个不可访问的对象就可能被回收,我们称不可访问的对象为非活动对象。
在浏览器环境中,GC Root 有很多,通常包括了以下几种等;
- 全局的 window 对象(位于每个 iframe 中);
- 文档 DOM 树,由可以通过遍历文档到达的所有原生 DOM 节点组成;
- 存放栈上变量;
第二步,回收非活动对象所占据的内存。其实就是在所有标记完成之后,统一清除内存中所有标记为可回收的对象
第三步,做内存整理。一般来说,频繁的回收对象后,内存中就会存在大量不连续空间,我们把这些不连续的内存空间称为内存碎片
。当内存中出现了大量的内存碎片之后,如果需要分配较大的连续内存时,就有可能出现内存不足的情况,所以最后异步需要整理这些内存碎片。但是这步其实是可选的,因为有的垃圾回收器不会产生内存碎片,比如副垃圾回收器。
以上就是大致的垃圾回收流程。目前 V8 采用了两个垃圾回收器,主垃圾回收器-Major GC 和副垃圾回收器-Minor GC(Scavenger)。