JavaScript的GC机制

101 阅读4分钟

在JavaScript七种基本类型中的引用类型Object的变量其占据内存空间大且大小不固定, 在堆内存中实际存储对象, 在栈内存中存储对象的指针, 对于对象的访问是按引用访问的. 在栈区中执行的变量等是通过值访问, 当其作用域销毁后变量也就随之销毁, 而使用引用访问的堆区变量, 在一个作用域消失后还可能在外层作用域或者其他作用域仍然存在引用, 不能直接销毁, 此时就需要通过算法计算该堆区变量是否属于不再需要的变量, 从而决定是否需要进行内存回收, 在JavaScript中主要有引用计数与标记清除两种垃圾回收算法

引用计数算法

对于引用技术垃圾回收算法, 把对象是否不再需要简化定义为该对象有没有其他变量或对象引用它, 如果没有引用指向该对象, 该对象将被垃圾回收机制回收. 在这里, 对象的概念不仅特指JavaScript对象, 还包括函数作用域或者全局词法作用域. 引用计数垃圾回收算法使用比较少, 主要是在IE6``与IE7```等低版本IE浏览器中使用

let obj = {
  a : {
    b: 11;
  }
}
// 此时两个对象被创建, a属性为对象1, obj为对象2, 两个对象都有被引用的变量, 都不能回收内存

let obj2 = obj;
// 对象2已经有obj和obj2两个变量的引用

obj = null;
// 将obj对于对象2的引用解除, 此时对象2还存在obj2一个引用

let a2 = obj2.a;
// 引用对象1, 此时对象1有a与a2两个引用

obj2 = null;
// 解除对象2的一个引用, 此时对象2的引用数量为0, 可以被垃圾回收
// 对象2的a属性引用被解除, 此时对象1只有a2一个引用

a2 = null;
// 解除a2对于对象1的引用, 此时对象1可以被垃圾回收

但是对于引用计数垃圾回收算法有个限制, 当对象循环引用时, 就会造成内存泄漏, 也就是引用计数垃圾回收算法无法处理循环引用的对象

let funct() {
  let obj = {};		// 命名为对象1, 此时引用数量为1
  let obj2 = {};	// 命名为对象2, 此时引用数量为1
  obj.a = obj2;		// obj的a属性引用obj2, 此时对象2的引用数量为2	
  obj2.a = obj;		// obj2的a属性引用obj, 此时对象1的引用数量为2
  
  return 1;
  // 此时执行栈的obj变量与obj2变量被销毁, 对象1与对象2的引用数量减1
  // 对象1的引用数量为1, 对象2的引用数量为1, 两个对象都不会被引用计数算法垃圾回收
}

funct();
// 两个对象被创建, 并互相引用, 形成了一个循环, 它们被调用之后会离开函数作用域, 所以它们已经不再需要了, 可以被回收了
// 然而引用计数算法考虑到它们互相都有至少一次引用, 所以它们不会被回收

标记清除算法

对于引用计数垃圾回收算法,把对象是否不再需要简化定义为该对象是否可以获得,该算法设置一个叫做根root的对象,在Javascript里根是全局对象,垃圾回收器将定期从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象,以此不断向下查找。从根开始,垃圾回收器将找到所有可以获得的对象和收集所有不能获得的对象,这样便解决了循环引用的问题。所有现代浏览器都使用了标记清除垃圾回收算法,所有对JavaScript垃圾回收算法的改进都是基于标记清除算法的改进

  • 垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记
  • 然后, 它会去掉运行环境中的变量以及被环境变量所引用的变量的标记
  • 此后, 依然有标记的变量就被视为准备删除的变量, 原因是在运行环境中已经无法访问到这些变量了
  • 最后, 垃圾收集器完成内存清除工作, 销毁那些带标记的值并回收它们所占用的内存空间