V8垃圾回收机制
作者:互联网
在堆中分为新生代—new_space和老生代—old_space以及其余分区
新生代内存用于存放一些生命周期比较短的对象数据--初生牛犊,新生代又分为Semi space From和Semi space To两部分
老生代内存用于存放一些生命周期比较长的对象数据--老油条,老生代又分为Old pointer space和Old data space两部分
内存大小
-
新老生代的内存大小与操作系统有关
-
64位新生代空间为64MB,老生代空间为1400MB
-
32位新生代空间为32MB,老生代空间为700MB
-
最新版node(v14)的内存为2GB
新生代的垃圾回收
回收策略
新生代采用Scavenger算法来管理内存,通过牺牲空间换取时间,代码中生成的变量会放到新生代的form区域中,当该区域放不下时,垃圾回收算法开始执行,通过广度优先遍历form区域和可达性分析,将所有的活跃对象拷贝到to区域,然后清空form区域,再然后将form区域与to区域进行身份对调
新生代晋升老生代
-
当一个活跃对象经历过拷贝,并且to区域的使用超过25%时,该新生代即可晋升为老生代
老生代垃圾回收
老生代的对象如何产生
-
新生代中活得久的晋升为老生代
-
新生代中放不下的直接分配到老生代
回收策略
-
老生代内存空间较大,不宜使用Scavenger算法,不仅空间浪费严重,拷贝所需时间也长
-
V8在老生代中采用Mark-Sweep(标记清理)和Mark-Compact(标记整理)相结合
Mark-Sweep
-
分为标记和清除两个阶段
-
标记阶段时,通过深度优先遍历堆中所有对象,标记出所有活跃对象,然后进入清除阶段,清除所有无标记对象
-
这种方法会出现内存碎片化的问题
Mark-Compact
-
标记整理解决了标记清除的内存碎片化问题
-
步骤与标记清除类似,但是多出了整理的阶段,通过整理直接覆盖垃圾区域,可以较少清除的操作
优化
在执行垃圾回收算法时,会暂停js脚本,这种现象称为全停顿—Stop the world,如果回收时间过长,会有卡顿现象,用户体验极差
Parallel(并行执行)
-
新生代的垃圾回收采取并行提升垃圾回收速度,他会开启多个辅助线程来执行新生代的垃圾回收工作
-
并行执行所需时间为总时间除以参与线程的数量加上管理时间
-
辅助线程工作期间依旧为全停顿
增量标记
-
老生代对象又大又多,垃圾回收时间长,所以采用增量标记方式优化
-
增量标记就是把标记工作分成多个阶段,每个阶段都只标记一部分对象,和主线线程的执行穿插进行
-
为支持增量标记,V8采用黑白灰三色标记法以支持垃圾回收的暂停和恢复
-
黑色表示这个节点被GC根引用到了,而且该节点的子节点都已经标记完成了
-
灰色表示这个节点被GC根引用到了,但子节点还没被垃圾回收器标记处理,也表明目前正在处理这个节点
-
白色表示此节点还没被垃圾回收器发现,如果本轮遍历完后仍为白色,就会被回收
-
-
引入灰色标记后,就可以通过判断有没有灰色节点来判断标记是否完成了,如果有灰色节点,下次恢复的应该从灰色节点执行
Write-barrier(写屏障)
-
当黑色指向白色节点的时候,就会触发写屏障,这个屏障会把白色节点设置为灰色
当 a 变黑 a.b = { name: 'b1' } 变灰时,a.b 指向了 { name: 'b2'},此时 { name: 'b2'} 将强行设置为灰色
global.a = { name: 'a' } global.a.b = { name: 'b1' } global.a.b = { name: 'b2' }
Lazy Sweeping(惰性清理)
-
当增量标记完成后,如果内存够用,先不清理,等JS代码执行完慢慢清理
concurrent(并发回收)
-
其实增量标记和惰性清理并没有减少暂停的总时间
-
并发回收就是主线程在执行过程中,辅助线程可以在后台完成垃圾回收工作
-
标记操作全部由辅助线程完成,清理工作由主线程和辅助线程配合完成(主线程参与是为了解决冲突)
并发和并行
-
并发和并行都是同时执行任务
-
并行的同时是同一时刻可以多个进程在运行
-
并发的同时是经过上下文快速切换,使得看上去多个进程同时都在运行的现象
标签:标记,新生代,回收,垃圾,V8,节点,老生 来源: https://blog.csdn.net/weixin_60901888/article/details/122752001