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;
}