在这之前先了解下什么是js中的垃圾?对于前端开发来说,js中内存管理是自动的,当创建对象、数组、函数等时就会自动给分配空间,当后续代码开发过程中对象不再被引用,或者不能从根上访问到时(不可达对象)这些都被称为垃圾。(javascript 中的根可以理解为是全局变量对象)
这里用到了可达对象这个词,那么js中的可达对象是什么?
可以访问到的对象就是可达对象(通过引用、作用域链),可达的标准就是从根出发能够被找到
GC
就是垃圾回收机制的简写,可以找到内存中的垃圾、并释放和回收空间。
GC里的垃圾是什么
- 程序中不再使用的对象
function fn(){
name='lj'
return `GC里的${name}是不再使用的对象`
}
fn()
当函数调用完成后,name变量不在被其他地方使用到。
- 程序中不能再访问到的对象
function fn(){
const name='lj'
return `GC里的${name}是不能访问到的对象`
}
fn()
name只能在fn函数内部访问,当fn 函数调用完成后,name 就成为不能再被访问的对象。
GC算法是什么
算法就是垃圾回收器在工作时查找和回收所遵循的规则。
常见的GC算法
-
引用计数
核心思想:通过引用计数器来维护引用数。设置引用数,判断引用计数是否为0;当引用关系发生改变时,引用计数器就会修改引用数字,当引用数字为0时,GC会立即工作将当前的对象空间进行回收。
const a=100; const b=20; const arr=[a,b]; function fn(){ const num = 0; } fn()
从上面这段代码的执行来分析,代码执行完毕后,fn函数中的常量num 的引用数字会为0,常量a,b还在被arr 使用,所以他们的引用数字肯定不是0,那么当整段代码执行完毕后,只有num 计数为0,它就会被当做垃圾进行清理。
优点:
- 发现垃圾时立即回收(数字为0),将内存立即释放
- 时刻监听着是否有垃圾,内存也就不会占满,最大限度减少程序卡顿时间
缺点:
- 无法回收循环引用的对象(计数永远不为0所以不能被回收)
- 时间开销大
//循环引用的对象 计数算法缺点 function fn(){ const obj1={} const obj2={} obj1.name=obj2 obj2.name=obj1 } fn()
当上面的代码执行完成后,fn 函数内部的obj1 和obj2 在外部就不能被访问到了,按照GC里的垃圾概念,其就可以视为垃圾被清除掉。但是从 引用计数算法来说 obj1 和obj2 的引用次数都不为0,使用引用计数算法的话,他们是不会被视为垃圾被清理的。
-
标记清除
核心思想:分标记和清除两个阶段完成,第一阶段会遍历所有的对象找到活动对象(也称为可达对象),第二阶段仍是遍历所有的对象,清除没有被标记的对象,回收相应的空间并清除所有标记。同时它会将回收的空间放在空闲列表中,方便后续程序在这个中申请空间使用。
优点:
- 相对于计数算法来说,解决了循环引用的对象不能被回收的缺点。
缺点:
- 空间碎片化(由于回收的垃圾对象在地址上是不连续的,分散在各个地方,当我们使用的时候可能会遇到空间大或者空间小的问题,使我们的空间不能得到最大化的使用。)
-
标记整理
核心思想:标记整理算法可以说是标记清除的增强,第一阶段与标记清除算法一致,第二阶段清除未标记对象前,会先执行整理,移动对象的位置,使回收的空间产生连续。
红色代表活动对象 棕色代表非活动对象 白色代表剩余空间。标记整理算法的整个过程如下图:
-
分代回收
这个算法思想在V8的垃圾回收策略中被使用。这里我们先来了解下什么是V8.
认识V8
- v8是一款主流的JavaScript 执行引擎
- v8采用即时编译
- v8内存设置上限
- v8采用基于分代回收思想实现垃圾回收
- v8内存分为新生代和老生代
V8的垃圾回收策略
采用分代回收的思想,将内存空间一分为二,左侧新生代(新生代指的是存活时间较短的对象,小空间用于存储新生代对象(32M|16M)(64位|32位)),右侧老生代(老生代指的是存活时间较长的对象 。64位操作系统1.4G,32位操作系统700M),,针对不同的部分采用不同的算法。
V8中新生代存储区垃圾回收的流程
- 回收过程采用复制算法+标记整理
- 新生代内存区分为二个等大小空间 , From 状态和To状态的两个空间
- 使用空间为From,空闲空间为To
- 如果代码在执行的时候需要申请空间使用,就会将所有的变量对象存储于From空间
- 当from空间使用空间量达到一定程度后,GC就会启动开始标记整理后(活动对象全部找到,进行整理)将活动对象拷贝至To空间
- 将From空间完成释放
V8如何回收老生代对象
- 主要采用标记清除、标记整理、增量标记算法
- 首先使用标记清除完成垃圾空间的回收
- 采用标记整理进行空间优化(当新生代对象晋升,需要移动到老生代对象中,出现空间不足的情况)
- 采用增量标记进行效率优化
对比:
- 新生代区域垃圾回收使用空间换时间
- 老生代区域垃圾回收不适合复制算法
V8中常用的GC算法
- 分代回收
- 空间复制
- 标记清除
- 标记整理
- 标记增量