Skip to content

浏览器的垃圾回收机制

垃圾回收又称为 GC(Garbage Collection),这里讲述的垃圾回收机制是基于 V8 引擎而言的。

理解垃圾回收机制可以帮助我们理解内存泄漏的问题以及手动预防和优化。

什么是垃圾回收机制

程序工作过程中会产生很多垃圾,这些垃圾是程序不用的内存或者之前用过的,它由引擎内部进行回收,因此对前端开发而言这个过程是相对无感的。而在别的语言中,比如 C、C++,通常需要开发者手动管理内存,相对比较麻烦。

垃圾的产生和回收

JavaScript 的对象数据是保存在堆内存中的,然后在栈内存中保存一个对堆内存中实际对象的引用,所以 JavaScript 对引用数据类型的操作都是操作对象的引用而不是实际的对象。

例如以下代码:

js
let test = {
  name: 'isboyjc'
}
test = [1, 2, 3, 4, 5]
let test = {
  name: 'isboyjc'
}
test = [1, 2, 3, 4, 5]

test 指向了另外一个堆内存的数据,那么之前堆内存的数据就就没有用了,如果不清理掉,类似的情况多了,就会导致内存溢出。

垃圾回收策略

JS 内存管理中有个概念叫可达性,就是那些以某种方式可访问或者说可用的值,它们被保证储存在内存中,反之则需要被回收。

垃圾回收的原理就是定期找到那些不会用到的内存,然后释放,其中涉及到一些算法策略:

  • 标记清除算法

  • 引用计数算法

V8 对垃圾回收的优化

分代式垃圾回收

V8 中将堆内存分为新生代老生代两个区域,采用不同的垃圾回收器管理垃圾回收。

之所以采用分代式的策略,因为新老生代的回收机制和频率是不一样的,因此可以很大程度地提高垃圾回收的效率。

新生代垃圾回收

新生代的对象为存活事件比较短的对象,老生代的对象为存活时间较长或者常驻内存的对象。通常经历过新生代垃圾回收后还存活下来的对象,容量通常比较大。

新生代对象通过一个名为Scavenge的算法进行垃圾回收,它将堆内存一分为二,使用区和空闲区,新加入的对象会放到使用区,当使用区快被写满时,就会执行一次垃圾清理操作。

垃圾回收时,新生代垃圾回收器会对使用区的活动对象做标记,标记完成后将使用区的活动对象复制到空闲区并进行排序,随后将非活动对象占用的空间清理掉,再进行角色互换,把原来的使用区变成空闲区。

当一个对象经过多次复制后依然存活,它将会被认为是生命周期较长的对象并移动到老生代中。

另一种情况是当一个对象在空闲区的空间占用超过了 25%,这个对象将直接晋升到老生代空间中。

新生代的回收采用了并行策略,启动多个线程来负责新生代的垃圾清理操作,大大缩短了回收的时间。

老生代垃圾回收

因为老生代的对象通常比较大,因此不能采用新生代那种复制来复制去的操作,否则效率不高,因此采用了标记清除算法。

标记算法就是从一组根元素开始,递归遍历这组根元素,遍历中能到达的元素称为活动对象,没有到达的元素可以判断为非活动对象被直接清除。

小结

垃圾回收的原理就是定期找到那些不会用到的内存,然后释放,主要是借助了标记清除和引用计数。

同时 V8 引擎通过分代式的回收机制,同时借助增量标记、惰性清理、并发回收的特性,提高了垃圾回收的效率。

但是并不是所有无用对象都可以被回收,当不再用到的内存没有被及时回收时,就出现了内存泄露

参考文章:

https://juejin.cn/post/6981588276356317214?searchId=20231116134841D8E1B5141A00E531BADE