垃圾回收机制
内存的生命周期
- 分配内存
- 内存的使用,即读取
- 释放内存
第2步在所有语言中都是明确的。
而对于第1和第3步,像C这样的低级语言,是需要手动分配内存和释放内存的。而像JavaScript这样的高级语言,分配内存和释放内存都是自动的。
在JavaScript中,值的声明和函数调用都会导致自动地分配内存。
决定内存何时不再需要了是困难的。低级语言让程序员来决定释放内存的时机,JavaScript让垃圾收集器来决定这个时机,但这也只是一个近似值,因为一块内存何时不再需要是无法确定的。
垃圾回收
在内存管理的上下文中,如果一个对象可以访问另一个对象(显式或隐式),那么我们说前者引用了后者。
引用计数算法
对象每多一个引用,这个对象的计数加1,每少一个引用,这个对象的计数减1。如果一个对象的计数为0,这个对象占用的内存就被视为“垃圾”,是可以被清除的。
引用计数算法有一个缺陷,当对象存在相互引用的情况下,尽管相互引用的对象不会再继续使用了,其计数仍然至少是1。这就导致相互引用的对象占用的内存无法被垃圾收集器回收,造成内存泄漏。IE6和7就使用引用计数垃圾收集器,现代浏览器没有再使用引用计数收集器的了。
function f() {
const x = {};
const y = {};
x.a = y; // x references y
y.a = x; // y references x
return 'azerty';
}
f();
标记清除算法
该算法将“不再需要对象”的定义简化为“无法访问对象”。
该算法假定知道一组称为根的对象。在 JavaScript 中,根是全局对象。定期地,垃圾收集器将从这些根开始,查找从这些根引用的所有对象,然后是从这些根引用的所有对象,依此类推。从根开始,垃圾收集器将因此找到所有可达对象并收集所有不可达对象对象。
再标记清除算法下,循环引用不再是问题了。以上面的代码为例,因为f()执行完后,在函数外部就无法从根对象访问到x和y了。