在上一回中,我们了解了 引用计数 的原理以及优缺点,但由于现代浏览器已经放弃了该方案,所以我们更应该对 标记清除 有更加深入的理解,这一篇文章就会详细讲解关于 标记清除 这种方案里面的门道,废话不多说,开搞!
标记清除
标记清除在实现过程上相较于引用计数更加复杂一点,大体流程分如下两个阶段
- 标记阶段:这个阶段用于给活动对象打上标记
- 清除阶段:这个阶段用于清除未打上标记的对象
标记阶段的实现过程是通过 一组根对象 (在浏览器环境里包括但不限于Window对象,dom对象)出发,通过引用关系去遍历出所有被引用到的对象,所有被遍历到的对象都会被打上标记,从而表示这个对象是 可达的
清除阶段的实现过程是通过遍历堆中的对象,清除所有未被打上标记的对象,清除完成后,重置标记,等待下一轮的GC
这种方式可以很好地解决引用计数的两个缺点:
- 计数器所需内存空间较大:标记清除法只需要打标记(打或者不打),因此只需要一个二进制位就可以实现
- 循环引用的问题:标记清除法是根据对象 可达性 来判断是否需要被清除的,所以循环引用并不会影响GC的回收
但凡事没有完美的,这种方案又会带来如下的问题:
- 清除过后的内存空间是 不连续的,呈现碎片化的状态
- 当需要新分配对象的内存时,需要遍历内存空间以查找合适的空间存放新对象
因为上述两种缺点,基于 标记清除 的方式又衍生出了 标记压缩 的方式
这种方式 标记阶段 跟标记清除是一样的,不同之处在于 清除阶段,这种方案会先将所有 可达对象 的内存空间整体移动到内存的一端,这样可达对象和不可达对象的内存空间就各自占据一块连续的空间,然后再去清除掉不可达对象所占用的空间,这样就可避免空间碎片化的问题,完美~
结语
技术的发展总是为了更好体验,也是一个不断追求极致的过程,所以标记压缩还不是终点,V8运用自己的黑科技把GC抬到了一个更高的高度,至于它是如何做的,请听下回分解😉