目录

🧭 JavaScript 垃圾回收机制

在 JavaScript 的内存管理是自动的、无形的。我们创建的原始值、对象、函数…… 这一切都会占用内存。当不需要某个东西, JavaScript 该如何发现并清理?

  • 垃圾:没有任何对象需要(没有任何变量引用它们的对象数据)

了解 JavaScript 的垃圾回收机制之前先来了解可达性:

# 可达性

JavaScript 中主要的内存管理概念是 可达性。「可达」意思是以某种方式可访问或可用的值,它们一定是存储在内存中的。

  • 固有的可达值基本集合(这些值显然不能被释放),这些值被称为(Root)。:

    • 当前执行的函数,它的局部变量和参数。

    • 当前嵌套调用链上的其他函数、它们的局部变量和参数。

    • 全局变量。

    • (还有一些内部的)

  • 还有可以通过引用或引用链从根访问任何其他值,也是可达的。

在 JavaScript 引擎中有一个被称作 垃圾回收器 (opens new window) 的东西在后台执行。它监控着所有对象的状态,并删除掉那些已经不可达的。

# 结合可达性与垃圾回收

🌰 例子:一个对象的引用

// user 具有对这个对象的引用
let user = {
  name: "John"
};
1
2
3
4

全局变量 "user" 引用了对象 {name:"John"} 。John 的 "name" 属性存储一个原始值,所以它被写在对象内部。

user = nulluser 的值被重写了,这个引用就没了。现在 John 变成不可达的了。因为没有引用了,就不能访问到它了。垃圾回收器会认为它是垃圾数据并进行回收,然后释放内存。

🌰 例子: 两个对象的引用:

// user 具有对这个对象的引用
let user = {
  name: "John"
};

let admin = user;
1
2
3
4
5
6

再执行 user = null 。此时,对象仍然可以被通过 admin 这个全局变量访问到( admin 仍然保留对 John 的引用),所以对象还在内存中。如果又重写了 adminadmin = null ),对象才会被删除。

🌰 例子:相互关联的对象:

function marry(man, woman) {
  woman.husband = man;
  man.wife = woman;

  return {
    father: man,
    mother: woman
  }
}

let family = marry({
  name: "John"
}, {
  name: "Ann"
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

marry 函数通过让两个对象相互引用使它们 “结婚” 了,并返回了一个包含这两个对象的新对象。如下图,所有对象都是「可达的」。

image-20220504175728585
  • 此时移除两个引用:

    delete family.father
    delete family.mother.husband
    
    1
    2

    可以看到「到达 John 的两条途径」显然都被移除了,所以此时 John 是不可达的。这是 John 将被从内存中移除,同时 John 的所有数据都将变得不可达。

  • 「无法到达的岛屿」:如果此时重写 family ,即

    family = null
    
    1

    此时虽然内部几个对象相互引用,但外部没有对其任意对象的引用,这些对象也可能是「不可达」的,并被从内存中删除。

# 内部算法

垃圾回收的基本算法被称为 「mark-and-sweep」。

定期执行以下「垃圾回收」步骤:

  • 垃圾收集器找到所有的根,并「标记」(记住)它们。
  • 然后它遍历并「标记」来自它们的所有引用。
  • 然后它遍历标记的对象并标记 它们的 引用。所有被遍历到的对象都会被记住,以免将来再次遍历到同一个对象。
  • …… 如此操作,直到所有可达的(从根部)引用都被访问到。
  • 没有被标记的对象都会被删除
📢 上次更新: 2022/09/02, 10:18:16