JS的垃圾回收机制

256 阅读3分钟

背景

我们都知道js的数据有两种:存在栈空间的原始类型数据 && 存在堆空间的引用类型数据,当数据不再使用的时候就会变成垃圾数据,这些数据一直保存在内存中会占用内存空间,所以需要回收垃圾数据来释放内存空间

不同的垃圾回收策略

不同的语言使用了不同的垃圾回收策略

C/C++语言中内存的分配和销毁都是由代码控制的,这就是手动回收策略

JS/Java/Python的垃圾数据是由垃圾回收器释放的,这就是自动回收策略

JS中的垃圾回收机制

栈中的回收策略

栈中有一个记录当前执行状态的指针:ESP,它指向调用栈中的当前执行上下文,当需要销毁当前执行上下文的时候ESP下移到下一个执行上下文,这时候虽然之前的执行上下文还在栈内,但是已经是无效数据,有新的执行上下文入栈的时候会直接覆盖该数据

总结:当一个函数执行结束的时候JS引擎通过下移ESP来销毁该函数存在栈中的执行上下文

堆中的回收策略

堆中的垃圾回收需要使用垃圾回收器

代际假说

堆中的垃圾回收建立在代际假说基础之上

什么是代际假说?

  1. 大部分对象在内存中很快就会不可用
  1. 短时间没有变成不可用数据的对象会存在更长时间

新生代

生存时间较短的对象,大对数对象会被分配到这里,垃圾回收频繁

使用Scavenge 算法:

新生代空间被划分为对象区域和空闲区域,新加入的对象会被放置在对象区域,对象区域快被写满的时候执行一次垃圾清理操作

如何清理?

  1. 标记对象区域的数据是存活的还是需要清理的
  1. 复制存活的数据有序的复制到空闲区域
  1. 然后空闲区域变成对象区域,对象区域变成空闲区域
  1. 经历两次垃圾回收依然存活的数据被晋升到老生代

老生代

生存时间较长或者比较大的对象

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

  1. 标记活动对象和垃圾数据
  1. 活动对象都向一端移动
  1. 清理端边界以外的内存

全停顿

JS脚本和垃圾回收都是运行在主线程上的,垃圾回收的时候正在执行的JS脚本要停止执行,等垃圾回收结束再继续执行

老生代的数据较大,垃圾回收时间较长,这种停顿会造成页面卡顿,于是产生了

增量标记(Incremental Marking)回收:

将垃圾标记过程分解为多个子过程,垃圾回收标记和JS脚本交替执行

并行回收:

在执行一个完整的垃圾回收过程中,垃圾回收器会使用多个辅助线程来并行执行垃圾回收。

并发回收(最难):

回收线程在执行 JavaScript 的过程,辅助线程能够在后台完成的执行垃圾回收的操作。