一.为什么会有垃圾?
一般指无法被访问的内存(如下),如果不清理这些数据,容易造成内存泄漏
1.没有被任何内容引用的对象
var person = {name: "a1"};
person = {name: "b2"};
2.全局变量
全局变量只有浏览器关闭或者刷新的时候才会被销毁,导致程序很难对全局变量是否为垃圾对象进行判断
3.闭包
js垃圾回收机制理论上不回收这类型的数据。因为闭包有一个特性是存在于闭包中的变量不会被回收。而实际上不同的浏览器实现的效果可能也不一样。一些浏览器如果发现某些变量没有且不会再被使用的话,也会将其回收。
4.部分基本数据类型
一个局部变量一旦脱离了创建它自身所在的环境(包括该环境内的子环境中),就会被视为垃圾,进行回收。比如我们在函数中定义的局部变量,当函数执行完毕后,这些变量就会被释放。由于在栈中,函数结束后本身就会释放堆栈内存,所以这种垃圾并不在我们经常讨论的范围内,一般垃圾回收的对象都是堆中的对象。
二.垃圾回收机制的策略
策略1:引用计数法
简述:当一个对象被引用一次后,引用数就+1,当被取消引用一次,引用数就 -1 =》当引用数为0时,就会触发垃圾回收机制进行回收
优点:立即发现垃圾 ; 对象的回收不需要另外的GC线程专门去做
缺点:当在循环引用(对象a引用对象b,而对象b又引用对象a)的情况下,引用次数就永远不能为0,导致无法数据回收,内存就无法释放了。
策略2:标记清除法
简述过程
1.将所有对象标记为 0
2.从根对象开始遍历将存活的对象标记为1
3.然后将标记为 0 的对象进行清除
4.最后将所有标记的对象标记为 0,方便下一次垃圾回收机制的回收
缺点:
清除完数据后,剩余的可用的数据不连续的,如果新加入新的对象很难找到位置。而且当回收的对象过多且位置分散,会导致性能过低。
如何为新对象找位置?
方法1 First-fit 👉 找到能放置下新对象的第一个块位置 (性能最好,主要采用这种)
方法2 Best-fit 👉 找到能放置下新对象的最小的块
方法3 Worst-fit 👉 找到最大的块,切一块空间给新对象 (容易产生更多不连续的空间)
三.V8对垃圾回收机制的优化
1.优化标记算法(在堆内存中分出两块区域来)
a.新生代区域
① 一般存放新的,存活时间短的对象
② 新生代区域的垃圾回收机制
分出两个空间 to空间(闲置空间) 和 from空间(使用空间)
当from空间中的对象要满的时候,就开始标记,将标记好的对象复制到 to空间最后再把from空间清空,然后再交换他们的名称
b.老生代区域
① 一般存放大的,老的,存活时间长的对象
② 老生代的垃圾回收机制(标记清除算法 和 标记压缩算法)
首先将所有对象标记为 0 ,将存活对象标记为 1,将标记为 1 对象全部清空,然后将标记为 1 的对象全部标记为 0 ,最后再用标记压缩算法是他们位置整理好
2.全停顿的优化
起因:
因为JS是单线程语言,所以在执行垃圾回收机制时,js执行会被暂停(一般称为全停顿)
优化:
垃圾回收机制支持多线程并行回收,可以使垃圾回收机制的时间加快,最大化保证js的执行。但是这样依然会堵塞js的执行
再优化 ----- 增量标记
增量标记,垃圾回收可以分段执行,执行一段js,就执行一次垃圾回收,使它们交叉执行,最大化保证js执行,但是如何记录上一次标记的位置
记录上一次位置 ---- 三色标记法
三色标记法将上一次标记到的位置标记为灰色,当下次执行的时候从灰色开始标记
并发回收
V8 中,js在主线程执行,垃圾回收可以在辅助线程执行。