🧭 JavaScript 垃圾回收机制
在 JavaScript 的内存管理是自动的、无形的。我们创建的原始值、对象、函数…… 这一切都会占用内存。当不需要某个东西, JavaScript 该如何发现并清理?
- 垃圾:没有任何对象需要(没有任何变量引用它们的对象数据)
了解 JavaScript 的垃圾回收机制之前先来了解可达性:
# 可达性
JavaScript 中主要的内存管理概念是 可达性。「可达」意思是以某种方式可访问或可用的值,它们一定是存储在内存中的。
固有的可达值基本集合(这些值显然不能被释放),这些值被称为根(Root)。:
当前执行的函数,它的局部变量和参数。
当前嵌套调用链上的其他函数、它们的局部变量和参数。
全局变量。
(还有一些内部的)
还有可以通过引用或引用链从根访问任何其他值,也是可达的。
在 JavaScript 引擎中有一个被称作 垃圾回收器 (opens new window) 的东西在后台执行。它监控着所有对象的状态,并删除掉那些已经不可达的。
# 结合可达性与垃圾回收
🌰 例子:一个对象的引用
// user 具有对这个对象的引用
let user = {
name: "John"
};
2
3
4
全局变量 "user"
引用了对象 {name:"John"}
。John 的 "name"
属性存储一个原始值,所以它被写在对象内部。
当 user = null
, user
的值被重写了,这个引用就没了。现在 John
变成不可达的了。因为没有引用了,就不能访问到它了。垃圾回收器会认为它是垃圾数据并进行回收,然后释放内存。
🌰 例子: 两个对象的引用:
// user 具有对这个对象的引用
let user = {
name: "John"
};
let admin = user;
2
3
4
5
6
再执行 user = null
。此时,对象仍然可以被通过 admin
这个全局变量访问到( admin
仍然保留对 John 的引用),所以对象还在内存中。如果又重写了 admin
( admin = null
),对象才会被删除。
🌰 例子:相互关联的对象:
function marry(man, woman) {
woman.husband = man;
man.wife = woman;
return {
father: man,
mother: woman
}
}
let family = marry({
name: "John"
}, {
name: "Ann"
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
marry
函数通过让两个对象相互引用使它们 “结婚” 了,并返回了一个包含这两个对象的新对象。如下图,所有对象都是「可达的」。
此时移除两个引用:
delete family.father delete family.mother.husband
1
2可以看到「到达 John 的两条途径」显然都被移除了,所以此时 John 是不可达的。这是 John 将被从内存中移除,同时 John 的所有数据都将变得不可达。
「无法到达的岛屿」:如果此时重写
family
,即family = null
1此时虽然内部几个对象相互引用,但外部没有对其任意对象的引用,这些对象也可能是「不可达」的,并被从内存中删除。
# 内部算法
垃圾回收的基本算法被称为 「mark-and-sweep」。
定期执行以下「垃圾回收」步骤:
- 垃圾收集器找到所有的根,并「标记」(记住)它们。
- 然后它遍历并「标记」来自它们的所有引用。
- 然后它遍历标记的对象并标记 它们的 引用。所有被遍历到的对象都会被记住,以免将来再次遍历到同一个对象。
- …… 如此操作,直到所有可达的(从根部)引用都被访问到。
- 没有被标记的对象都会被删除。