垃圾回收
对于开发者来说,JavaScript 的内存管理是自动的、无形的。我们创建的原始值、对象、函数……这一切都会占用内存。 当我们不再需要某个东西时会发生什么?JavaScript 引擎如何发现它并清理它?
可达性(Reachability)
JavaScript 中主要的内存管理概念是 可达性。 简而言之,“可达”值是那些以某种方式可访问或可用的值。它们一定是存储在内存中的。
- 这里列出固有的可达值的基本集合,这些值明显不能被释放。
比如说:
-
当前执行的函数,它的局部变量和参数。
-
当前嵌套调用链上的其他函数、它们的局部变量和参数。
-
全局变量。
-
(还有一些内部的)
这些值被称作 根(roots)。
- 如果一个值可以通过引用链从根访问任何其他值,则认为该值是可达的。比方说,如果全局变量中有一个对象,并且该对象有一个属性引用了另一个对象,则 该 对象被认为是可达的。而且它引用的内容也是可达的。下面是详细的例子。
// user 具有对这个对象的引用
let user = {
name: "John"
};
// user 引用了对象 {name:“john”}
// 如果 user 的值被重写这个引用就没了
user = null
// 现在 John 变成不可达的了。因为没有引用了,就不能访问到它了。
// 垃圾回收器会认为它是垃圾数据并进行回收,然后释放内存。
无法到达的岛屿
几个对象相互引用,但外部没有对其任意对象的引用,这些对象也可能是不可达的,并被从内存中删除。 源对象与上面相同。
内部算法
垃圾回收的基本算法被称为 “mark-and-sweep”。 定期执行以下“垃圾回收”步骤:
- 垃圾收集器找到所有的根,并“标记”(记住)它们。
- 然后它遍历并“标记”来自它们的所有引用。
- 然后它遍历标记的对象并标记 它们的 引用。所有被遍历到的对象都会被记住,以免将来再次遍历到同一个对象。
- ……如此操作,直到所有可达的(从根部)引用都被访问到。
- 没有被标记的对象都会被删除。
例如,使我们的对象有如下的结构:
我们可以清楚地看到右侧有一个“无法到达的岛屿”。现在我们来看看“标记和清除”垃圾收集器如何处理它。
第一步标记所有的根:
然后,我们跟随它们的引用标记它们所引用的对象:
……如果还有引用的话,继续标记:
现在,无法通过这个过程访问到的对象被认为是不可达的,并且会被删除。
这是垃圾收集工作的概念。JavaScript 引擎做了许多优化,使垃圾回收运行速度更快,并且不会对代码执行引入任何延迟。