JS 垃圾回收机制(Garbage Collecation)

86 阅读5分钟

一、前言

应用程序在运行过程中需要占用一定的内存空间,并且在运行完后可以将不再用到的内存释放掉

否则会导致:一方面会影响到程序的运行速度,另一方面如果严重的话会导致整个程序的崩溃

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 增量垃圾收集缺点

重要缺点是会增加数据的复杂性,使数据变得不透明。可能会使化,因为处理器需要理解这些额外的标记

还可能会破坏数据的不变性,因为一旦标记了数据,很难再去除这些标记而不影响数据本身

一般会尽量避免使用增量标记,或者尽可能使用透明的数据结构和操作来处理数据。