一、前言
应用程序在运行过程中需要占用一定的内存空间,并且在运行完后可以将不再用到的内存释放掉
否则会导致:一方面会影响到程序的运行速度,另一方面如果严重的话会导致整个程序的崩溃
1.1 GC机制
部分语言(如C语言)需要手动释放内存,但是相对麻烦,所以很多语言就提供自动内存管理机制,称为“垃圾回收机制”,JavaScript 也提供了垃圾回收机制(Garbage Collecation),简称GC机制
JavaScript 的垃圾回收机制是自动管理内存的一种方式,可以帮助开发者避免常见的内存泄漏问题。
1.2 全停顿(Stop The World)
所谓全停顿:垃圾回收算法在执行前,需要将应用逻辑暂停,执行完垃圾回收后再执行应用逻辑,这种行为称为全停顿(Stop The World)
例如,一次GC需要50ms,应用逻辑就会去暂停50ms;是为了解决应用逻辑与垃圾回收机制看到的情况不一样的问题
举例来说:我们在吃自助的时候,将想要的食材取回来的时候,发现餐具被服务员收走了。
这里,服务员就好比垃圾回收器,餐具就是我们的应用逻辑。在我们自己看来,只是将餐具临时放在餐桌上,单服务员看来,我们已经不再使用这些餐具,就收拾走了。我们与服务员对用一个事物看到了是不一样的情况。导致服务员做了与我们预期不一样的事情。
所以,为了避免应用逻辑与垃圾回收器看到的情况不一致,垃圾回收算法在执行时候,需要停止应用逻辑。
二、JavaScript 中的垃圾回收
2.1 引用计数-早期浏览器常用
JS引擎中会有一长“引用表“,保存内存里面所有资源(通常为各种值)的引用次数。如果一个值的引用次数为0,就表示这个值不再使用了,因此就可以将这块内存释放掉。
const use1 = {name: '小张'}
const use2 = {name: '小王'}
const use1 = {name: '小李'}
const userList = [use1.name, use2.name, use3.name]
上述代码,当执行完一遍后,use1、use2、use3都被userList引用了,所以它们的引用计数不为零,不会被回收
引用计数算法优点
1.引用计数为零时,发现垃圾立即回收
2.最大限度减少程序暂停
引用计数算法缺点
1.开销比较大:需要我们单独拿出一片空间来维护每个变量的引用计数,对于比较大的程序,空间开销很大
2.循环引用
function objGroup(obj1, obj2) {
obj1.a = obj2
obj2.b = obj1
return {
o1: obj1,
o2: obj2
}
}
let obj = objGroup({name: 'obj1'}, {name: 'obj2'})
console.log(obj, 111)
上述代码,obj1和obj2通过各自的属性相互引用,所以引用计数都不是零,都不会被垃圾回收机制回收,造成内存浪费
2.2 标记清除(Mark-and-Sweep)
核心思想:标记和清除两个阶段完成
当变量进入环境(例如,函数中声明一个变量)时,变量被标记为“在使用”,而当变量离开环境时,如果没有其他引用指向它,垃圾收集器会在未来的某个时间收集它并释放所占用的内存。
核心思想:标记和清除两个阶段完成
1.标记阶段:垃圾收集器会遍历所有可访问的对象,并标记他们
2.清除阶段:垃圾收集器会遍历整个内存空间,将所有未标记的对象当作垃圾收集起来
2.2.1 标记清除算法优点
对比引用计数算法,最大的优点是能够回收循环引用对象,也是v8引擎使用最多的算法
是一种分布式算法,标记阶段不会进行回收 ,从而不会打断正常的执行流程
此外,可以有效的处理循环数据结构,因为每个对象只会被标记一次,而后续的清除操作会重复使用标记信息
2.2.2 标记清除算法缺点
1.两个阶段的开销导致效率不高
2.标记和清除的过程中会产生大量不连续的内存碎片,可能导致后续过程无法分配足够大的内存空间
2.3 标记整理(Mark-Compact)
可以看作是标记清除的增强。标记阶段的操作和标记清除一致
清除阶段会先执行整理,移动对象位置,将存活的对象移动到一边,再清除端边界外的内存
2.3.1 标记整理的优点
解决内存碎片的问题,提高内存的利用
2.3.2 标记整理的优点
移动对象位置,不立即回收对象,回收的效率会慢
2.4 增量垃圾收集(Incremental Marking)
为了减少全停顿时间,v8对标记进行了优化,将一次停顿进行的标记过程,分成了很多小步。每执行完一小步就让应用逻辑执行一会,这样交替完成标记
长时间的 GC,会导致应用暂停或无响应,最终导致糟糕的用户体验。2011年起,v8就将「全暂停」标记换成了增量标记。改进后,最大停顿时间减少到原来的1/6
2.4.1 增量垃圾收集优点
节省内存、提高新能
2.4.2 增量垃圾收集缺点
重要缺点是会增加数据的复杂性,使数据变得不透明。可能会使化,因为处理器需要理解这些额外的标记
还可能会破坏数据的不变性,因为一旦标记了数据,很难再去除这些标记而不影响数据本身
一般会尽量避免使用增量标记,或者尽可能使用透明的数据结构和操作来处理数据。