天天看点

前端进阶之垃圾回收机制

概念

原始数据类型是存储在栈空间中的,引用类型的数据是存储在堆空间中的。

不过有些数据被使用之后,可能就不再需要了,我们把这种数据称为垃圾数据。

如果这些垃圾数据一直保存在内存中,那么内存会越用越多。

所以我们需要对这些垃圾数据进行回收,以释放有限的内存空间。

这时候就引入了垃圾回收机制。

栈垃圾回收

当函数执行结束,JavaScript 引擎通过向下移动 

ESP

 指针(记录调用栈当前执行状态的指针),来销毁该函数保存在栈中的执行上下文(变量环境、词法环境、

this

outer

)。

function fn(){
  var a = 1;
  var b = { age: '2' };
  function fn2(){
    var c = 3;
    var d = { name: 'xiaoming' };
  }
  fn2();
}
fn();
           

在上面代码中,根据浏览器策略:

  • 先到全局执行上下文
  • 再到 foo 函数执行上下文
  • 最后到 showName 函数执行上下文

根据栈的特性,此时 JavaScript 情况应为:

调用栈

fn2

 函数执行上下文
fn函数执行上下文
全局执行上下文

释放过程:

  1. 执行到 fn2 函数的时候,创建 fn2 函数的执行上下文,并将 fn2 函数的执行上下文压入栈中。
  2. 以此同时,我们会有一个记录当前执行状态的指针(称为 ESP),指向调用栈中 fn2 函数的执行上下文,表示当前正在执行 fn2 函数。
  3. 当 fn2 函数执行完毕,函数执行流程进入 fn 函数,这时候 ESP 就下移到 fn 函数的执行上下文。这个下移操作就是销毁 fn2 函数执行上下文的过程。

如果 fn2 函数下面还调用了另一个函数,那么它会覆盖 fn2 函数的内容,用来存放另一个函数的执行上下文。

就是说:当一个函数执行结束之后,JavaScript 引擎会通过向下移动 

ESP

 来销毁保存栈中的执行上下文。

堆垃圾回收

上述代码中的对象依旧存在堆中要回收对象的垃圾数据,就需要 JavaScript 中的垃圾回收器。

V8 中会把堆分为 新生代 和 老生代 两个区域,新生代中存放的是生存时间短的对象,老生代中存放的生存时间久的对象。

新生区的容量没有老生区那么大,所以 V8 分别使用 2 个不同的垃圾回收器,来高效实施垃圾回收:

  • 副垃圾回收器:主要负责新生代的垃圾回收。
  • 主垃圾回收器:主要负责老生代的垃圾回收。

新生代 - 副垃圾回收器

算法:Scavenge 算法。

原理:

  • 把新生代空间对半划分为两个区域,一半是对象区域,一半是空闲区域。
  • 新加入的对象都会存放到对象区域,当对象区域快被写满时,就需要执行一次垃圾清理操作。
  • 先对对象区域中的垃圾做标记,标记完成之后,把这些存活的对象复制到空闲区域中
  • 完成复制后,对象区域与空闲区域进行角色翻转,也就是原来的对象区域变成空闲区域,原来的空闲区域变成了对象区域。

对象晋升策略:经过两次垃圾回收依然还存活的对象,会被移动到老生区中。

老生代 - 主垃圾回收器

算法:标记 - 清除(Mark-Sweep)算法。

原理:

  1. 标记:标记阶段就是从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素称为活动对象,没有到达的元素就可以判断为垃圾数据。
  2. 清除:将垃圾数据进行清除。

对一块内存多次执行标记 - 清除算法后,会产生大量不连续的内存碎片。而碎片过多会导致大对象无法分配到足够的连续内存。

这时候就需要标记整理。

算法:标记 - 整理(Mark-Compact)算法

原理:

  1. 标记:和标记 - 清除的标记过程一样,从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素标记为活动对象。
  2. 整理:让所有存活的对象都向内存的一端移动
  3. 清除:清理掉端边界以外的内存

全停顿

由于 JavaScript 是单线程的,所以一旦执行垃圾回收算法,那正在执行的 JavaScript 脚本需要暂停下来,等垃圾回收完毕之后再恢复脚本执行。

这种行为叫 全停顿(Stop-The-World)。

如果堆中的数据较多,那么回收需要时间,会造成页面卡顿状态,所以为了降低这个卡顿,V8 将标记过程分为一个一个子标记过程,同时垃圾回收标记和 JavaScript 应用逻辑交替进行。

优化算法:增量标记(Incremental Marking)算法

原理:

  1. 为了降低老生代的垃圾回收而造成的卡顿
  2. V8 把一个完整的垃圾回收任务拆分为很多小的任务
  3. 让垃圾回收标记和 JavaScript 应用逻辑交替进行