垃圾回收器
JavaScript在执行中会对原有数据进行删除、修改,这时就产生了垃圾。
var obj = {a:1}
obj = {b:233}
这样{a:1}就成为了垃圾,在堆内存中保存着
标记-删除
以前使用引用计数,但是解决不了循环引用的问题
var a={b:1}
var b={a:1}
a.b=b
这样循环套娃,a与b都保持了引用。
就不会为这是个垃圾数据,就会造成内存泄露。
标记
现在使用标记删除的做法。从GC Root对象开始遍历,对访问到的就标记为活动对象,遍历完成后对访问不到的就是非活动对象。
GC Root
- window对象
- DOM树
- 存放在栈上的数据
window.a.b.c=123
那遍历的顺序就是
window -> a -> b -> c
删除
删除非活动对象就完了内存清理,但是会带来一个问题。
堆中的内存分布是随机的,并不像栈一样是连续的。这就生成了内存碎片
整理
最后垃圾回收器要整理内存碎片,才能有效的利用内存。
红色的是垃圾数据,灰色的是活动数据。当红色的删除后就会产生大量的不连续内存,称之为内存碎片。
这就需要垃圾回收器来进行整理排序了,操作也是很耗时间的。
因此针对不同的对象数据,V8使用了正副两种垃圾回收器
垃圾分类
对于JavaScript执行过程中生成的垃圾根据生命周期可以分为两类
新生代
生命周期短的。如同蜉蝣朝生暮死,转瞬即逝
这类数据分为新生代数据中保存,由副垃圾回收器-Minor GC (Scavenger)的Scavenge 算法处理。
老生代
生命周期长的。比如window对象等,这类数据特点是数据量大且存在时间久。使用主垃圾回收器-Major GC,标记 - 清除(Mark-Sweep)。
副垃圾回收器
采用Scavenge 算法,将新生代分为两块一半是对象区域 (from-space),一半是空闲区域 (to-space)。
所有数据存放在对象区域,当达到阈值时开始执行清理。
标记
开始遍历对象区域内的数据,将访问到的存入空闲区。
翻转
空闲区与对象区翻转。
清空
将空闲区内数据清空,这样就完成了新生区的垃圾回收工作,并且在标记阶段就完成了排序解决了内存碎片问题。
主垃圾回收器
主垃圾回收器由于数据量庞大不能使用Scavenge 算法处理,也没有这个必要给老生代中的数据分配一个同等内存大小的空闲区域。
技术最重要的是取得平衡,最大程度的提升性能。
主垃圾回收器的流程就是上面的标记-清理-整理的流程
而且垃圾回收器是执行在主线程的,那意味着会阻塞主线程上的任务。而且完成一次标记-清理-整理大约需要200ms。
好了结束了
这里讲一下垃圾回收器的基础,下一篇文章讲V8在近几年是如何一步步优化垃圾回收器的。
(因为有些多在2015年、2017年、2019年都做了优化,等我读完。秋梨膏)
参考文献
极客时间《图解 Google V8》已经出完了,大家可以买来康康。
不过解释宏任务和微任务哪里不是很详细,给大家推个视频 www.bilibili.com/video/BV1a4…