引言:对于 C 语言这般的低级语言,一般可以通过比如 malloc() 和 free()可以在内存中释放我们定义的变量。但是JavaScript的垃圾回收是自动进行的,那么js在何时回收变量呢?
首先来看一下内存的生命周期:
- 分配你所需要的内存
- 使用分配到的内存(读、写)
- 不需要时将其释放\归还
所以,我们只要知道js引擎怎么判断内存变量不再需要,就知道js是在什么时候回收内存变量了。
我们先来说一种古老的垃圾回收机制:
引用计数垃圾收集
这是最简单的垃圾收集算法。此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”。如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。
我们看一段代码来分析:
// 两个对象被创建,一个作为另一个的属性被引用,另一个被分配给变量o
// 很显然,没有一个可以被垃圾收集
var o = {
a: {
b:2
}
};
// o2变量是第二个对“这个对象”的引用
var o2 = o;
// 现在,“这个对象”的原始引用o被o2替换了
o = 1;
// 引用“这个对象”的a属性
// 现在,“这个对象”有两个引用了,一个是o2,一个是oa
var oa = o2.a;
// 最初的对象现在已经是零引用了
// 他可以被垃圾回收了
// 然而它的属性a的对象还在被oa引用,所以还不能回收
o2 = "yo";
oa = null;
// a属性的那个对象现在也是零引用了
// 它可以被垃圾回收了
引用计数垃圾收集有一个很大的缺陷:无法处理循环引用
比如,无法处理下列代码:
function f(){
var o = {};
var o2 = {};
// o 引用 o2
o.a = o2;
// o2 引用 o
o2.a = o;
}
f();
其实,引用计数垃圾收集 是古老的IE 6, 7 采用的垃圾手机机制,去他喵的IE6,7
接下来我们介绍一个更现代化的垃圾收集机制:
标记-清除垃圾收集
这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”。
这个算法假定设置一个叫做根(root)的对象(在Javascript里,根是全局对象)。定期的,垃圾回收器将从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和所有不能获得的对象。
这个算法比前一个要好,因为“有零引用的对象”总是不可获得的,但是相反却不一定。怎么理解呢?比如这段引用计数没法回收的代码:
function f(){
var o = {};
var o2 = {};
// o 引用 o2
o.a = o2;
// o2 引用 o
o2.a = o;
}
f();
// 定期从window对象开始,o和o2不可获得,回收
从2012年起,所有现代浏览器都使用了标记-清除垃圾回收算法。所有对JavaScript垃圾回收算法的改进都是基于标记-清除算法的改进,并没有改进标记-清除算法本身和它对“对象是否不再需要”的简化定义。