前言
何谓JavaScript垃圾,很简单,我们创建基元,对象,函数,使用完后不被需要了,但是因为种种原因,得不到释放,一直在内存中占用内存。便是垃圾。例如,我们使用完了一个对象或者函数,但是使用后,一直没有通过置为null来释放。
可达性
JavaScript中内存管理的主要概念是可达性。
简而言之,“可达”值是指可以某种方式访问或使用的值。确保将它们存储在内存中。
-
有一组固有的固有值,由于明显的原因而无法删除。
例如:- 当前函数的局部变量和参数。
- 当前嵌套调用链中其他函数的变量和参数。
- 全局变量。
-
如果其他值可以通过引用或引用链从根访问,则认为该值是可以到达的。最典型的就是闭包。函数内部访问了外部的变量。外部的变量就不会释放。
JavaScript引擎中有一个称为垃圾收集器的后台进程。它监视所有对象,并删除那些变得无法访问的对象。
示例:
let user = {
name: "John"
};
全局变量"user"引用该对象{name: "John"}。"name"的属性存储一个基元John。
如果的值user被覆盖,则引用丢失:
user = null;
现在对象变得不可及。无法访问它,也没有对其的引用。垃圾收集器将垃圾数据并释放内存。
现在,假设我们将引用从复制user到admin:
let user = {
name: "John"
};
let admin = user;
现在,如果我们执行相同的操作:
user = null;
然后该对象仍然可以通过admin全局变量访问,因此它在内存中。如果我们也覆盖admin,则可以将其删除。
互连对象
现在是一个更复杂的示例:
function marry(man, woman) {
woman.husband = man;
man.wife = woman;
return {
father: man,
mother: woman
}
}
let family = marry({
name: "John"
}, {
name: "Ann"
});
到目前为止,所有对象都是可访问的。
现在让我们删除两个引用:
delete family.father;
delete family.mother.husband;
因此,John现在无法访问,并且将从其内存中删除的所有数据也变得不可访问。
垃圾回收后:
无法到达的岛屿
源对象与上面相同。然后:
family = null;
内存中的图片变为:
显然,John和Ann仍然保持链接,两者都有传入的引用。但这还不够。前一个"family"对象已与根断开连接,不再引用它,因此整个岛将变得不可访问并将被删除。
内部算法
基本的垃圾收集算法称为“标记清除”。
定期执行以下“垃圾收集”步骤:
- 垃圾收集器扎根并“标记”(记住)它们。
- 然后,它访问并“标记”来自它们的所有引用。
- 然后,它访问标记的对象并标记其引用。记住所有访问过的对象,以免将来再次访问同一对象。
- …依此类推,直到访问了所有可到达的引用(从根开始)。
- 除标记对象外的所有对象均被删除
例如,让我们的对象结构如下所示:
我们可以清楚地在右侧看到一个“无法到达的岛屿”。
-
第一步标记了全局变量上的所有引用的基本类型和引用类型,即为根源:
-
然后标记根源的引用:
-
现在,在该过程中无法访问的对象被认为是不可访问的,并将被删除:
我们可以想象该过程是从根部依据引用进行查找,标记了所有可到达的对象。未标记的然后被删除。
优化
这就是垃圾收集工作原理的概念。JavaScript引擎进行了许多优化,以使其运行更快且不影响执行。
- 分代收集–将对象分为两组:“新的”和“旧的”。许多物体出现,工作并迅速死亡,因此可以积极地清理它们。那些生存时间足够长的人会变得“老”,接受检查的频率降低。
- 增量收集–如果有很多对象,并且我们尝试同时遍历并标记整个对象集,可能会花费一些时间,并在执行过程中引入明显的延迟。因此,引擎尝试将垃圾收集分为几部分。然后,分别地执行这些片段。这需要在它们之间进行额外的标记以跟踪更改,但是我们有很多微小的延迟,而不是很大的延迟。
- 空闲时间收集–垃圾收集器仅在CPU空闲时尝试运行,以减少对执行的可能影响。