js垃圾回收机制

本文参考链接:https://segmentfault.com/a/1190000018605776?utm_source=tag-newest

一、首先明确什么是垃圾?

那些没有被任何变量或者属性引用的对象就是垃圾,哪怕几个对象形成一个环形引用,但如果根访问不到他们,那也算是垃圾。

二、理解js内存的可达性机制

js的可达性,简单说就是那些可以以某种方式访问到可用的值,这样的值会被保存在内存中。

如果某个值可以通过引用或者引用链被根访问到,那么这个值就是可访问的

举例子:

一个引用:

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

 箭头表示对象引用,全局变量“user”引用对象 {name:“John”},所以这个对象会被保存在内存中,user的值被覆盖引用会丢失

user = null;

 现在john变成了不可达状态,没办法引用访问,所以垃圾回收器将john丢弃,释放内存。

两个引用:

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

let admin = user;

 这种情况下,user = null,对象还是可以引用的。

相互关联的对象:

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

  return {
    father: man,
    mother: woman
  }
}

let family = marry({
  name: "John"
}, {
  name: "Ann"
})

函数 marry 通过给两个对象彼此提供引用来“联姻”它们,并返回一个包含两个对象的新对象

产生内存结构

 目前所有对象都是可访问的

试着删除两个引用

 此时仍然都是可访问的

 但是如果我们把这两个都删除,那么我们可以看到 John 不再有传入的引用:

垃圾回收之后:

                          图片描述

无法访问的数据块:

 

三、内部算法:

1、标记-清除

这是当前主流的GC算法,V8里面就是用这种。当对象,无法从根对象沿着引用遍历到,即不可达(unreachable),进行清除。

基本的垃圾回收算法称为“标记-清除”,定期执行以下“垃圾回收”步骤:

  • 垃圾回收器获取根并“标记”(记住)它们。
  • 然后它访问并“标记”所有来自它们的引用。
  • 然后它访问标记的对象并标记它们的引用。所有被访问的对象都被记住,以便以后不再访问同一个对象两次。
  • 以此类推,直到有未访问的引用(可以从根访问)为止。
  • 除标记的对象外,所有对象都被删除。

简单说就是从根开始逐个访问,根能访问到的就标记,根访问不到的就是垃圾,要被清除。

例如,对象结构如下:

图片描述

我们可以清楚地看到右边有一个“不可到达的块”。现在让我们看看“标记并清除”垃圾回收器如何处理它。

第一步标记根

图片描述

然后标记他们的引用

图片描述

以及子孙代的引用:

图片描述

现在进程中不能访问的对象被认为是不可访问的,将被删除:

图片描述

这就是垃圾收集的工作原理。JavaScript引擎应用了许多优化,使其运行得更快,并且不影响执行。

一些优化:

  • 分代回收——对象分为两组:“新对象”和“旧对象”。许多对象出现,完成它们的工作并迅速结 ,它们很快就会被清理干净。那些活得足够久的对象,会变“老”,并且很少接受检查。
  • 增量回收——如果有很多对象,并且我们试图一次遍历并标记整个对象集,那么可能会花费一些时间,并在执行中会有一定的延迟。因此,引擎试图将垃圾回收分解为多个部分。然后,各个部分分别执行。这需要额外的标记来跟踪变化,这样有很多微小的延迟,而不是很大的延迟。
  • 空闲时间收集——垃圾回收器只在 CPU 空闲时运行,以减少对执行的可能影响。

 2、引用计数

另一种不太常见的垃圾回收策略是引用计数。引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减1。当这个引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其所占的内存空间给收回来。这样,垃圾收集器下次再运行时,它就会释放那些引用次数为0的值所占的内存。

不积跬步无以至千里
原文地址:https://www.cnblogs.com/lyt0207/p/12022859.html