V8 采用的垃圾回收算法 —— 分代回收(Generation GC)
# 一. 什么是垃圾
确定不会再被使用的数据,即为垃圾数据。
栈(stack)会自动分配内存空间,会自动释放空间。无需垃圾回收。 堆(heap)动态分配的内存,大小不定也不会自动释放。需要垃圾回收机制来释放内存空间。 注:闭包中的变量并不保存在栈内存中,而是保存在堆内存中。这也是闭包能引用到函数内变量的原因。
# 二、算法分类
常见的 JS 垃圾回收基础算法有两类:标记清除、引用计数
由于基础算法是对全部对象进行遍历,而JS引擎是单线程的,执行垃圾回收时页面会处在卡顿状态,执行时间越长卡顿现象越久,因此,实际各家浏览器的垃圾回收策略都是在基础算法的基础上做了优化:
V8 的垃圾回收机制:分代回收
分代回收机制:
- 将内存分为“新生代区”和“老生代区”,分别存储“新生代对象”和“老生代对象”;
- 老生代区占用的内存较大,最初存储 JS 原始对象;
- 新生代区占用的内存较小,且又被分为“活跃区”和“非活跃区”,最初活跃区存放所有新创建的对象,非活跃区为空;
- 当新生代的活跃区被占满后,将触发垃圾回收,过程如下:
- 交换新生代的“活跃区”和“非活跃区”
- 对“非活跃区”的对象进行“标记清除”,过程如下:
* 从根节点开始遍历对象
* 未被标记过的活跃对象放回“活跃区”
* 已被多次标记的活跃对象放进“老生代区”
* 标记出不再活跃的对象,然后统一清除,此时新生代的活跃区只剩下活跃的对象
- 当老生代被占满后,触发垃圾回收,“标记清楚”过程如下:
- 从根节点开始遍历对象
- 标记出不再活跃的非原始对象(不能清除原始对象),然后统一清除,此时老生代区只剩下原始对象和活跃的对象
- 垃圾回收的最后,要进行内存整理,使不连续的内存空间变为连续空间,便于再次使用
# 三、 标记清除如何回收垃圾
# 1. 标记
V8 采用 可达性 算法来判断堆中对象应不应该被回收,判断步骤如下:
- 从根节点出发,遍历所有的对象
- 可以遍历到的对象,标识为可达(reachable)
- 无法遍历到的对象,标识为不可达(unreachable)
浏览器环境下,根节点包括:
- 全局对象 window,位于每个 iframe 中
- 文档 DOM 对象
- 存放在栈中的变量
根节点不是垃圾,不能被回收。
# 2. 清理
标记完成后,统一清理被标识为不可达的对象。
# 3. 内存整理
清理操作后,堆内存中存在大量不连续的空间,称为 内存碎片
https://juejin.im/post/6844903695721709581#heading-15
https://jinlong.github.io/2016/05/01/4-Types-of-Memory-Leaks-in-JavaScript-and-How-to-Get-Rid-Of-Them/
https://zhuanlan.zhihu.com/p/33816534