GC是垃圾收集的意思(Garbage Collection)
1. 标记清除法
js中最常用的垃圾回收方式就是标记清除。当变量进入环境时,例如,在函数中声明一个变量,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。
function test(){
var a = 10 ; // 被标记 ,进入环境
var b = 20 ; // 被标记 ,进入环境
}
test(); // 执行完毕 之后 a、b又被标离开环境,被回收。
标记的方式有很多种,这是最常见的:
垃圾回收器在运行的时候会给存储在内存中的所有变量都加上标记(当然,可以使用任何标记方式)。然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记(闭包)。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾回收器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。
我们更多的是关注其策略:
引擎在执行 GC(使用标记清除算法)时,需要从出发点去遍历内存中所有的对象去打标记,而这个出发点有很多,我们称之为一组 根 对象,而所谓的根对象,其实在浏览器环境中包括又不止于 全局Window对象、文档DOM树 等
整个标记清除算法大致过程就像下面这样:
(1)垃圾收集器在运行时会给内存中的所有变量都加上一个标记,假设内存中所有对象都是垃圾,全标记为0
(2)然后从各个根对象开始遍历,把不是垃圾的节点改成1
(3)清理所有标记为0的垃圾,销毁并回收它们所占用的内存空间
(4)最后,把所有内存中对象标记修改为0,等待下一轮垃圾回收
优点
标记清除算法的优点只有一个,那就是实现比较简单,打标记也无非打与不打两种情况,这使得一位二进制位(0和1)就可以为其标记,非常简单
缺点
标记清除算法有一个很大的缺点,就是在清除之后,剩余的对象内存位置是不变的,也会导致空闲内存空间是不连续的,出现了 内存碎片(如下图),并且由于剩余空闲内存不是一整块,它是由不同大小内存组成的内存列表,这就牵扯出了内存分配的问题
2. 引用计数
记录引用的次数 为0是垃圾
var o1 = {
o2: {
x: 1
}
};
//创建2个对象o2和o1,其中o2被o1对象引用作为其属性,此时没有垃圾可收集
var o3 = o1; //创建变量o3,引用由o1指向的对象的变量
o1 = 1; //现在将o1重新赋值为1,最初的o1中的对象由o3变量表示
var o4 = o3.o2; //创建变量o4,引用对象o2,此时o2被两个地方引用:一个是作为o3变量的属性,一个是作为o4变量
o3 = '666'; // 此时最初o1对象应没有再被引用了,可以被垃圾收集了,但是最初的o2还在被o4引用,因此还不能被垃圾收集
o4 = 16; //此时 o2也可以说再见了...
优点
引用计数算法的优点我们对比标记清除来看就会清晰很多,首先引用计数在引用值为 0 时,也就是在变成垃圾的那一刻就会被回收,所以它可以立即回收垃圾
而标记清除算法需要每隔一段时间进行一次,那在应用程序(JS脚本)运行过程中线程就必须要暂停去执行一段时间的 GC,另外,标记清除算法需要遍历堆里的活动以及非活动对象来清除,而引用计数则只需要在引用时计数就可以了
缺点:
(1)它需要一个计数器,而此计数器需要占很大的位置,因为我们也不知道被引用数量的上限
(2)还有就是无法解决循环引用无法回收的问题,这也是最严重的
let a = {
b:{}
}
a.b = a
//这时 a=null 对于引用计次法无效 因为b一直引用a