V8垃圾回收机制
V8垃圾回收机制
垃圾回收机制
目标:清除不再使用的对象,使腾退出所占用的内存
在浏览器的发展历史上有两种解决策略:
- 标记清除
- 引用计数
标记清除
标记清除分为:标记阶段和清除阶段。
首先它会遍历堆内存上所有的对象,分别给它们打上标记,然后在代码执行过程结束之后,对所使用过的变量取消标记。在清除阶段再把具有标记的内存对象进行整体清除,从而释放内存空间。
使用标记清除策略的最重要的优点在于简单,无非是标记和不标记的差异。通过标记清除之后,剩余的对象内存位置是不变的,也会导致空闲内存空间是不连续的,这就造成出现内存碎片的问题。内存碎片多了后,如果要存储一个新的需要占据较大内存空间的对象,就会造成影响。对于通过标记清除产生的内存碎片,还是需要通过标记整理策略进行解决。
简而言之:
- 优点:简单
- 缺点:内存碎片化、分配速度慢
标记整理
经过标记清除策略整理后,老生代内存中因此产生了许多内存碎片,如果不进行清理内存碎片,就会对存储造成影响。
标记整理(Mark-Compact)算法 就可以有效地解决标记清除的两个缺点。它的标记阶段和标记清除算法没有什么不同,只是标记结束后,标记整理算法会将活着的对象(即不需要清理的对象)向内存的一端移动,最后清理掉边界的内存。
引用计数
引用计数是一种不常见的垃圾回收策略,其思路就是对每个值都记录其的引用次数。具体的:
- 当变量进行声明并赋值后,值的引用数为1。
- 当同一个值被赋值给另一个变量时,引用数+1
- 当保存该值引用的变量被其它值覆盖时,引用数-1
- 当该值的引用数为0时,表示无法再访问该值了,此时就可以放心地将其清除并回收内存。
这种回收策略看起来很方便,但是当其进行循环引用时就会出现问题,会造成大量的内存不会被释放。当函数结束后,两个对象都不在作用域中,A 和 B 都会被当作非活动对象来清除掉,相比之下,引用计数则不会释放,也就会造成大量无用内存占用,这也是后来放弃引用计数,使用标记清除的原因之一。
V8对于垃圾回收机制的优化
分代式垃圾回收
V8 的垃圾回收策略主要基于分代式垃圾回收机制,V8 中将堆内存分为新生代和老生代两区域,采用不同的垃圾回收器也就是不同的策略管理垃圾回收。
新生代的对象为存活时间较短的对象,简单来说就是新产生的对象,通常只支持 1~8M 的容量,而老生代的对象为存活事件较长或常驻内存的对象,简单来说就是经历过新生代垃圾回收后还存活下来的对象,容量通常比较大。
V8 整个堆内存的大小就等于新生代加上老生代的内存,对于新老两块内存区域的垃圾回收,V8 采用了两个垃圾回收器来管控。
新生代内存回收的原理是:
- 新加入的对象都会存放在使用区,当使用区快写满时就进行一次垃圾清理操作。
- 在开始进行垃圾回收时,新生代回收器会对使用区内的对象进行标记
- 标记完成后,需要对使用区内的活动对象拷贝到空闲区进行排序
- 而后进入垃圾清理阶段,将非活动对象占用的内存空间进行清理
- 最后对使用区和空闲区进行交换,使用区->空闲区,空闲区->使用区
老生代内存回收的原理是:使用Mark-Sweep(标记清除)和Mark-Compact(标记整理)的策略
首先是标记阶段,从一组根元素开始,递归遍历这组根元素,遍历过程中能到达的元素称为活动对象,没有到达的元素就可以判断为非活动对象。清除阶段老生代垃圾回收器会直接将非活动对象,也就是数据清理掉。
同样的标记清除策略会产生内存碎片,因此还需要进行标记整理策略进行优化。
另外,因为JS是单线程语言,所以在执行垃圾回收机制的时候,JS执行会被暂停,称为全停顿。而V8机制对这种情况做了一定的优化,垃圾回收机制支持多线程并行回收,提高了垃圾回收的效率。但是仍然会阻塞JS的执行,所以就诞生了增量标记。增量标记可以使得垃圾回收可以分段执行,最大化保证了JS的执行。V8机制还支持了并发回收。JS在主线程执行,垃圾回收在辅助线程执行