JS 加油包之垃圾回收 (Garbage Collection)

268 阅读2分钟

什么是“垃圾”

1. 数据可达性

  • 如果一段数据是可达的(reachability), 那么我们称之为有用数据;反之如果不可达,则是无用数据,也就是垃圾。

2. 根

当一个值满足以下几种情况之一时,这个值称之为根数据(roots):

  • 全局变量;
  • 当前函数当原有变量和参数;
  • 当前链中其他函数的变量和参数;
  • 其他内部变量

3. 垃圾回收

  • 如果数据既不是根数据,也没有引用到根数据,那么该数据不可达,即需要被 JS 引擎回收;
  • 垃圾回收是 JS 引擎的一个后台进程。引擎会监控所有的对象,删去不可达的数据。
  • 垃圾回收的意义在于:重复利用无用的内存
  • 🌰 举个例子:
let user = { name: "John" };
let admin = user;  // 关系如下图

let user = null; // 如果只加这一行,该对象不是垃圾,因为它还被admin还引用了
let admin = null;  //  如果同时写这两行,那么该对象没有引用到根数据,会被垃圾回收
  • 🌰 🌰 再举个例子:
function marry(man, woman) {
  woman.husband = man;
  man.wife = woman;
  return { father: man, mother: woman }
}

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

(1) John和Ann是夫妻,组成了一个家庭family(引用了全局变量),如果他们有孩子,他们将会成为父亲和母亲;
(2) 当家庭把John除名以后,家庭不承认John的父亲身份,Ann也不承认John是她的丈夫(如下图)

(3) 那么此时,虽然John单方面认为Ann是他的妻子,但是因为family和Ann都没有引用John(不承认和John有关系),此时的John虽然引用了Ann,却是一个不可达的数据,会被 JS 引擎回收掉~
(4) 如果John和Ann互相都承认夫妻关系,也承认他们组建的家庭,但是他们的家庭不被全局变量承认(如下图,当family:null时⬇️ ),那么很遗憾,这个家庭的数据全部都会被回收掉

如何“捡垃圾”

  1. 从全局变量出发,标记引用了根数据的对象(可用遍历或计数的方法);
  2. 遍历后,没有被遍历的对象为不可达对象(unreachables),会被回收;或使用计数法,没有引用指向该对象,或者说引用数为0的,就是不可达对象
  3. 遍历/计数时,要注意引用(箭头)的方向,只有根数据指向的对象,才是可达的(reachable)(如下图)


小结

  1. 垃圾回收是自动运行的,我们无法操作它的开始与结束;
  2. 可达的数据会被保存;
  3. “被引用”和“从根数据可达”是不一样的,一段“被引用”的数据,可能是“不可达”的 (Being referenced is not the same as being reachable)。

参考文章 | Reference List
The JavaScript language: javascript.info/garbage-col…
MDN 内存管理: developer.mozilla.org/zh-CN/docs/…