背景
我们都知道js的数据有两种:存在栈空间的原始类型数据 && 存在堆空间的引用类型数据,当数据不再使用的时候就会变成垃圾数据,这些数据一直保存在内存中会占用内存空间,所以需要回收垃圾数据来释放内存空间
不同的垃圾回收策略
不同的语言使用了不同的垃圾回收策略
C/C++语言中内存的分配和销毁都是由代码控制的,这就是手动回收策略
JS/Java/Python的垃圾数据是由垃圾回收器释放的,这就是自动回收策略
JS中的垃圾回收机制
栈中的回收策略
栈中有一个记录当前执行状态的指针:ESP,它指向调用栈中的当前执行上下文,当需要销毁当前执行上下文的时候ESP下移到下一个执行上下文,这时候虽然之前的执行上下文还在栈内,但是已经是无效数据,有新的执行上下文入栈的时候会直接覆盖该数据
总结:当一个函数执行结束的时候JS引擎通过下移ESP来销毁该函数存在栈中的执行上下文
堆中的回收策略
堆中的垃圾回收需要使用垃圾回收器
代际假说
堆中的垃圾回收建立在代际假说基础之上
什么是代际假说?
- 大部分对象在内存中很快就会不可用
- 短时间没有变成不可用数据的对象会存在更长时间
新生代
生存时间较短的对象,大对数对象会被分配到这里,垃圾回收频繁
使用Scavenge 算法:
新生代空间被划分为对象区域和空闲区域,新加入的对象会被放置在对象区域,对象区域快被写满的时候执行一次垃圾清理操作
如何清理?
- 标记对象区域的数据是存活的还是需要清理的
- 复制存活的数据有序的复制到空闲区域
- 然后空闲区域变成对象区域,对象区域变成空闲区域
- 经历两次垃圾回收依然存活的数据被晋升到老生代
老生代
生存时间较长或者比较大的对象
使用标记 - 整理(Mark-Compact)算法:
- 标记活动对象和垃圾数据
- 活动对象都向一端移动
- 清理端边界以外的内存
全停顿
JS脚本和垃圾回收都是运行在主线程上的,垃圾回收的时候正在执行的JS脚本要停止执行,等垃圾回收结束再继续执行
老生代的数据较大,垃圾回收时间较长,这种停顿会造成页面卡顿,于是产生了
增量标记(Incremental Marking)回收:
将垃圾标记过程分解为多个子过程,垃圾回收标记和JS脚本交替执行
并行回收:
在执行一个完整的垃圾回收过程中,垃圾回收器会使用多个辅助线程来并行执行垃圾回收。
并发回收(最难):
回收线程在执行 JavaScript 的过程,辅助线程能够在后台完成的执行垃圾回收的操作。