V8 内存管理(垃圾回收机制)
V8 也会申请内存,申请的内存又会分为堆内存和栈内存
1.1 栈
- 栈用于存放 JS 中的基本类型和引用类型指针
- 栈的空间是连续的,增加删除只需要移动指针,操作速度非常快
- 栈的空间是有限的,当栈满了,就会抛出一个错误
- 栈一般是在执行函数时创建的,在函数执行完毕后,栈就会被销毁
1.2 堆
- 堆主要用于存储 JS 中的引用类型
1.2.1 堆空间分类
1.2.1.1 新生代(new space)
- 新生代内存用于存放一些生命周期比较短的对象数据
1.2.1.2 老生代(old space)
- 老生代内存用于存放一些生命周期比较长的对象数据
- 当new space的对象进行两个周期的垃圾回收后,如果数据还存在new space中,则将他们存放到old space中
- Old Space 使用标记清除和标记整理的方式进行垃圾回收
1.2.2 什么是垃圾
- 在程序运行过程中肯定会用到一些数据,这些数据会放在堆栈中,但是在程序运行结束后,这些数据就不会再被使用了,那些不再使用的数据就是垃圾
1.2.3 新生代的垃圾回收
- 新生代内存有两个区域,分别是对象区域(from) 和 空闲区域(to)
- 新生代内存使用Scavenger 算法来管理内存,垃圾回收的入口广度优先遍历 From-Space 中的对象,从根对象出发,广度优先遍历所有能到达的对象,把存活的对象复制到 To-Space遍历完成后,清空 From-SpaceFrom-Space 和 To-Space 角色互换
- 复制后的对象在 To-Space 中占用的内存空间是连续的,不会出现碎片问题
- 这种垃圾回收方式快速而又高效,但是会造成空间浪费(有 To-Space 空闲区域)
- 新生代的 GC 比较频繁
- 新生代的对象转移到老生代称为晋升 Promote,判断晋升的情况有两种经过一次 GC 还存活的对象对象复制到 To-Space 时,To-Space 的空间达到一定的限制(超过 25%)
1.2.4 老生代的垃圾回收
V8 在老生代中的垃圾回收策略采用Mark-Sweep(标记清除)和 Mark-Compact(标记整理)相结合
1.2.4.1 Mark-Sweep(标记清除)
- 标记清除分为标记和清除两个阶段
- 在标记阶段需要遍历(深度优先遍历)堆中的所有对象,并标记那些活着的对象,然后进入清除阶段。在清除阶段总,只清除没有被标记的对象
- V8 采取的是黑色和白色来标记数据,垃圾收集之前,会把所有的数据设置为白色,用来标记所有的尚未标记的对象,然后会从 GC 根出发,以深度优先的方式把所有的能访问到的数据都标记为黑色,遍历结束后黑色的就是活的数据,白色的就是可以清理的垃圾数据
- 由于标记清除只清除死亡对象,而死亡对象在老生代中占用的比例很小,所以效率较高
- 标记清除有一个问题就是进行一次标记清楚后,内存空间往往是不连续的,会出现很多的内存碎片。如果后续需要分配一个需要内存空间较多的对象时,如果所有的内存碎片都不够用,就会出现内存溢出的问题
1.2.4.2 Mark-Compact(标记整理)
- 标记整理正是为了解决标记清除所带来的内存碎片的问题
- 标记整理在标记清除的基础进行修改,将其的清除阶段变为紧缩极端
- 在整理的过程中,将活着的对象向内存区的一段移动,移动完成后直接清理掉边界外的内存
- 紧缩过程涉及对象的移动,所以效率并不是太好,但是能保证不会生成内存碎片,一般 10 次标记清理会伴随一次标记整理
1.2.5 优化
- 在执行垃圾回收算法期间,JS 脚本需要暂停,这种叫 Stop the world(全停顿)
- 如果回收时间过长,会引起卡顿
- 性能优化把大任务拆分小任务,分步执行,类似 fiber将一些任务放在后台执行,不占用主线程
1.2.5.1 Parallel(并行执行)
- 新生代的垃圾回收采取并行策略提升垃圾回收速度,它会开启多个辅助线程来执行新生代的垃圾回收工作
1.2.5.2 增量标记
- 老生代因为对象又大又多,所以垃圾回收的时间更长,采用增量标记的方式进行优化
- 增量标记就是把标记工作分成多个阶段,每个阶段都只标记一部分对象,和主线程的执行穿插进行
- 为了支持增量标记,V8 必须可以支持垃圾回收的暂停和恢复,所以采用了黑白灰三色标记法黑色表示这个节点被 GC 根引用到了,而且该节点的子节点都已经标记完成了灰色表示这个节点被 GC 根引用到了,但子节点还没被垃圾回收器标记处理,也表明目前正在处理这个节点白色表示此节点还没未被垃圾回收器发现,如果在本轮遍历结束时还是白色,那么这块数据就会被收回
- 引入了灰色标记后,就可以通过判断有没有灰色节点来判断标记是否完成了,如果有灰色节点,下次恢复的应该从灰色节点继续执行
1.2.5.3 Write-barrier(写屏障)
- 当黑色指向白色节点的时候,就会触发写屏障,这个写屏障会把白色节点设置为灰色
1.2.5.4 Lazy Sweeping(惰性清理)
- 当增量标记完成后,如果内存够用,先不清理,等 JS 代码执行完慢慢清理
1.2.5.5 concurrent(并发回收)
- 其实增量标记和惰性清理并没有减少暂停的总时间
- 并发回收就是主线程在执行过程中,辅助线程可以在后台完成垃圾回收工作
- 标记操作全都由辅助线程完,清理操作由主线程和辅助线程配合完成
文章出自:前端餐厅,如有转载本文请联系前端餐厅ReTech今日头条号。
github:https://github.com/zuopf769