1. 垃圾回收机制
简言之,垃圾回收就是把我们不需要再用到的对象释放内存
那怎么判断对象是否不再需要呢?
主要有两种方法
- 引用计数法
- 标记清除法
2. 引用计数法
以前的垃圾回收机制主要是用引用计数法
这种方法判断对象是否不再需要 是通过判断对象是否被引用来实现的
在内存环境里,对象如果被其他对象引用了,说明这个对象我们还需要它,反之则不需要它
注意,这里的对象不只是js对象,也包括函数作用域
看一个例子
// 两个对象被创建
// 1. 一个作为另一个的属性(male)被引用,称之为对象A
// 2. 另一个被分配给变量obj,称之为对象B
var obj = {
male: {
age: 20
}
}
// obj2和obj同时指向对象A
var obj2 = obj
// obj失去对象A的引用(因此也失去了对B的引用)
obj = 1
// p和obj2.male 同时指向对象B
var p = obj2.male
// obj2失去对象A的引用(因此也失去了对B的引用)
// 现在对象A可以被垃圾回收了
obj2 = null
// 现在对象B可以被垃圾回收了
p = null
但是这种清除方式会存在一个问题,循环引用
o和o2相互引用,因此无法回收。
function f(){
var o = {};
var o2 = {};
o.a = o2; // o 引用 o2
o2.a = o; // o2 引用 o
return "azerty";
}
f();
在IE6,7中,垃圾回收机制采用的是引用计数法,因此会出现内存泄露的情况
例子
var div;
window.onload = function(){
div = document.getElementById("div");
div.circularReference = div;
div.lotsOfData = new Array(10000).join("*");
};
div 这个 DOM 元素里的 circularReference 属性引用了 div,造成了循环引用
因此lotsOfData无法得到释放,内存会一直占着
3. 标记清除法
现在主流浏览器都是使用的标记清除法来作为js的垃圾回收机制
这种方法判断对象是否不再需要 是通过判断对象是否可以到达来实现的
标记清除法算法由标记阶段和清除阶段组成
标记阶段
首先从根(也就是全局对象)开始将可能被引用的对象用递归的方式进行标记,
清除阶段
将没有标记到的对象作为垃圾进行回收。
垃圾收集器建立了一个根节点
列表。根节点通常是那些引用被保留在代码中的全局变量。对于 Javascript 而言,Window
对象就是一个能作为根节点的全局变量例子。
垃圾收集器定时运行标记阶段的算法,递归的检查子节点。 下图就是该算法的执行动作
显而易见,全局变量作为根节点是不会被垃圾回收的,平时在开发的过程中,小心使用全局变量
参考文章:
JavaScript 内存管理
JavaScript 内存泄露问题
JavaScript是如何工作的:内存管理 + 如何处理4个常见的内存泄漏(译)