GC 算法
GC 算法就是垃圾回收机制的简写,GC 可以找到内存中的垃圾,并且释放,回收空间
GC 中认为是垃圾的标准
- 程序中不在需要使用的对象,当函数执行结束后,
name
就不在被使用,因此也被认为是垃圾
例如:
function func(){
name = 'silan'
return `${name} is a coder`
}
func()
2 . 程序中不能再访问到的对象,执行完func()
,函数外部就访问不到name
,因此也被认为是垃圾
function func(){
const name = 'silan'
return `${name} is a coder`
}
func()
常见的 GC 算法
- 引用计数
- 标记清除
- 标记整理
- 分代回收
引用计数算法实现原理
核心思想就是设置引用数,判断当前引用数是否为 0,通过一个引用计数器,当引用关系发生改变的时候,就会修改引用计数器的数字
引用关系改变就是说,有个对象,当有个变量指向它时,引用计数加 1,以此类推。让引用数字为 0 的时候立即进行回收
来看下面的代码,nameList 里面每个数组元素都是 user1,user2,user3 对象里面的属性,因此 user1,user2,user3,引用次数都为 1
fn 函数体里面的 num1 ,num2 因为是在函数体里面,没用使用到,因此引用次数为 0,但是 num3,num4 是挂载在全局对象上,可以通过全局对象访问到,因此引用次数为 1
const user1={age:12}
const user2 = {age:13}
const user3={age:14}
const nameList=[user1.age,user2.age,user3.age] //引用次数都为1
function fn(){
const num1= 1 // 引用次数0
const num2 =2 // 引用次数0
num3=3 //引用次数1
num4=4 //引用次数1
}
fn()
其实还有个更简单的方法,编辑器已经告诉我们num1 已经被定义但是没有使用过
,因此引用计数为 0
优缺点:
优点:
- 发现垃圾时立即回收
- 最大限度减少程序暂停,当发现内存即将到临界点的时候,就开始进行引用计数清除。
缺点:
- 时间复杂度比较高
- 无法回收循环引用的对象,比如 a.test =b , b.test =a
- 资源消耗大
标记清除实现原理
实现原理有两个阶段,
- 先遍历所有活动对象,做上标记
- 遍历所有对象清除没有标记的对象
最后回收相应的空间
标记清除优缺点
优点:
- 可以解决循环引用问题
缺点:
不会立即回收垃圾对象,即使发现了垃圾对象,也要等到遍历完才进行清除,会导致程序卡顿
产生空间碎片化,不能得到空间最大化效率的使用
可以看下这张图,假设 A 对象和 C 对象都没有变量引用,因此标记清除算法认为他们是可回收空间,回收之后正好是 3 个域空间,等下次程序申请空间的时候,在分配出来,但是 b 对象空间和 c 对象空间正好被 a 对象隔开,因此当程序申请 3 个空间时,就会发现两个域空间都不够,并且不连续。
这就是空间碎片化问题
标记整理算法
标记整理算法可以看做是标记清除算法的增强,标记整理算法流程跟标记清除算法差不多
- 遍历所有对象,给活动对象打上标记
- 遍历所有对象, 整理空间,根据标记移动对象位置,让地址上产生连续
- 移除非活动对象,回收空间
优缺点
优点:
- 减少碎片化空间
缺点:
1.不会立即回收垃圾对象,跟标记清除一样存在的问题
V8 引擎回收策略 (分代回收)
V8 是一款主流的 JavaScript 执行引擎,V8 采用即时编译,但是内存设限,64 位操作系统 1.5G,32 位系统 800M。也是因为内存有限制的原因,在回收策略上面使用了分代回收
分代回收,就是把内存分为新生代,老生代,小空间用于存储新生代对象,在不同系统上面也有不同的大小(64 位 32M,32 位 16M),然后针对不同对象采用不同 GC 算法
新生代对象:指的是存活时间比较短的对象,比如局部变量
老生代对象: 指的是存活时间比较长的对象,比如全局作用域下的变量,和闭包
v8 常用的 GC 算法
- 分代回收
- 空间复制
- 标记清除
- 标记整理
- 增量标记
v8 如何回收新生代对象
from 空间就是活动空间,to 为空闲空间,活动对象都存储于 from 空间,这两个空间是等大的。
一但触发垃圾回收时,先讲 From 空间进行标记整理,然后将活动对象拷贝至 to 空间,接着释放 from 空间,最后在进行 from 和 to 的空间交换,原来的 from 就变成了 to,原来的 to 就变成了 from,这也就是空间复制算法。
值得注意的是,在进行复制算法的时候,有可能会存在对象晋升
,也就是将新生代对象移动至老生代对象存储空间,晋升有两种触发条件:
一轮 GC 操作后还存活的新生代对象
当 to 空间使用率超过 25%的时候,因为复制算法是要把使用空间里面的活动对象拷贝至 to 空间,当使用空间超过 25%,to 空间也超过 25%,那么就存放不下了。
v8 如何回收老生代对象
老生代对象存储于老生代对象存储区域,见上图。大小在不同系统上面也有所不同,62 位为 1.4G,32 位为 700M
主要采用标记清除算法进行垃圾回收,当新生代对象晋升到老生代存储区域时,如果空间不足,就会进行标记整理操作,优化空间,最后采用增量标记提高效率
因为垃圾回收是会阻塞程序运行,因此垃圾回收和程序运行交替进行,没有一次性进行标记清除,而是在程序运行空隙进行增量标记,然后在标记完成后在进行垃圾回收,目的是为了尽可能小的去打断程序运行,增加效率
本文使用 mdnice 排版