stay hungry stay foolish

js垃圾回收机制

JavaScript引擎常用的垃圾回收机制有两种,一种是标记清除法,一种是引用计数法,有的浏览器会同时使用这两种。

标记清除法

标记清除算法从名称上看,可以拆分为两部分:标记(mark)和清除(sweep)。

此算法可以分为两个阶段,一个是标记阶段,一个是清除阶段,下面就分别做一下介绍。

  • 标记阶段:

在此阶段,垃圾回收器会从mutator(应用程序)根对象开始遍历。每一个可以从根对象访问到的对象都会被添加一个标识,于是这个对象就被标识为可到达对象。

  • 清除阶段:

在此阶段中,垃圾回收器,会对堆内存从头到尾进行线性遍历,如果发现有对象没有被标识为可到达对象,那么就将此对象占用的内存回收,并且将原来标记为可到达对象的标识清除,以便进行下一次垃圾回收操作。

在使用标记清除算法时,未引用对象并不会被立即回收.取而代之的做法是,垃圾对象将一直累计到内存耗尽为止.当内存耗尽时,程序将会被挂起,垃圾回收开始执行。

特别说明:在垃圾回收阶段,应用程序的执行会暂停,等待回收执行完毕后,再恢复程序的执行。

引用计数

记录每个对象被引用的次数,每次被创建引用的时候加1,有不引用它的就减1,当减到0的时候这个对象就应该被垃圾回收。

引用计数法的弊端

环形引用:

function marry(man, woman) {
  woman.husband = man;
  man.wife = woman;

  return {
    father: man,
    mother: woman
  }
}

let family = marry({
  name: "John"
}, {
  name: "Ann"
});
family = null;

如果使用的标记清除法,只要中断了这个环与外界的引用,那么整个环都是垃圾;如果是用引用计数法,则会发生内存泄露。

低版本IE(ie9之前)中有一部分对象并不是原生JS对象。例如,其BOM和DOM中的对象就是使用C++以COM(Component Object Model)对象的形式实现的,而COM对象的垃圾收集机制采用的就是引用计数策略。

因此即使IE的js引擎是用的标记清除来实现的,但是js访问COM对象如BOM,DOM还是基于引用计数的策略的,也就是说只要在IE中设计到COM对象,也就会存在循环引用的问题。

IE循环引用典型例子:

window.onload = function outerFunction(){
    var obj = document.getElementById("eleId");
    obj.onclick = function innerfunction(){
        console.log(obj.id);
    }
}

由于监听函数innerfunction引用到了obj,从而形成了闭包,导致obj不能被释放,从而形成了循环引用,这时即使把window.onload赋值为null,obj也无法释放,可以改成如下方式,避免内存泄露:

window.onload = function outerFunction(){
    var obj = document.getElementById("eleId");
    var id = obj.id;
    obj.onclick = function innerfunction(){
        console.log(id);
    }
    obj = null;
}