JavaScript
通过自动内存管理实现内存分配和闲置资源回收,基本思路很简单:确定哪个变量不会再使用,然后释放它占用的内存。
全局变量会在浏览器或者页面关闭的时候释放,而局部变量只会在函数执行过程中存在,所以垃圾回收一般说的是函数内部的变量回收机制。
这个过程是周期性的,即垃圾回收程序每隔一定时间就会自动运行。
在浏览器的发展史上,用到过两种主要的标记策略:标记清理和引用计数。
标记清理
垃圾回收程序运行的时候,会标记变量当前是进入环境还是离开环境,接着排除全局变量和闭包AO中的变量,然后开始清理。
function test() {
var a = 0; // 标记进入环境
}
test(); // 标记 a 离开环境
var b = 0;
var c = 1;
// 最后排除全局和闭包AO中的变量,然后清理标志离开环境的变量
引用计数
引用计算是对每个值都记录它被引用的次数。声明变量并给它赋一个引用值时,这个值的引用数为 1。如果同一个值又被赋给另一个变量,那么引用数加 1。类似地,如果保存对该值引用的变量被其他值给覆盖了,那么引用数减 1。当一个值的引用数为 0 时,就说明没办法再访问到这个值了,因此可以安全地收回其内存了。垃圾回收程序下次运行的时候就会释放引用数为 0 的值的内存。
function test() {
var a = new Object(); // a = 1;
var b = new Object(); // b = 1;
var c = a; // a + 1 = 2
c = b; // a - 1 = 1, b + 1 = 2;
}
test();
引用计数很快就遇到了严重的问题:循环引用。所谓循环引用,就是对象 A 有一个指针指向对象 B,而对象 B 也引用了对象 A。
function test() {
var a = new Object(); // a = 1;
var b = new Object(); // b = 1;
// 循环引用
a.prop = b; // b = 2
b.prop = a; // a = 2
// 解除引用
a = null;
b = null;
}
test();
以上代码 `a`和`b`在函数结束后还会存在,因为它们的引用数永远不会变成 0。如果函数被多次调用,则会导致大量内存永远不会被释放。