Brief Intro:
- js 引用数据类型保存在堆内存中,栈内保存的是实际对象的引用,每次造作都是操作对象的引用而不是实际对象
- 程序运行需要内存,操作系统必须提供内存
Strategy 策略
Mark-Clean 标记清除法
分为标记和清除两个阶段
标记:为所有活动对象做上标记
清除:把没有标记的对象销毁
Process 过程
- 垃圾收集器在运行时会给内存中的所有变量都加上一个标记,假设内存中所有对象都是垃圾,全标记为 0
- 然后从各个根对象开始遍历,把不是垃圾的节点改成 1
- 清理所有标记为 0 的垃圾,销毁并回收它们所占用的内存空间
- 最后,把所有内存中对象标记修改为 0,等待下一轮垃圾回收
Advantage 优点
实现简单,只分为打标记和不打标记两种情况
Disadvantage 缺点
内存 碎片化:空间内存块是不连续的,容易出现 内存碎片
分配时间慢:需要进行一次单项便利找到大于等于 size 的块才能为其分配
标记整理(Mark-Compact)算法 可以有效的解决上面的问题,标记结束后,将活着的对象向内存的一段移动,最后清理边界的内存
Reference-Counting 引用计数法
Process 过程
- 当声明了一个变量并且将一个引用类型赋值给该变量的时候这个值的引用次数就为 1
- 如果同一个值又被赋给另一个变量,那么引用数加 1
- 如果该变量的值被其他的值覆盖了,则引用次数减 1
- 当这个值的引用次数变为 0 的时候,说明没有变量在使用,这个值没法被访问了,回收空间,垃圾回收器会在运行的时候清理掉引用次数为 0 的值占用的内存
Advantage 优点
- 比标记清除清晰很多,引用为 0 时就会被回收
- 标记清除需要隔一段时间进行一次,并且需要遍历堆里的 活动以及非活动对象来清除
Disadvantage 缺点
- 需要一个计数器,该计数器需要占很大的位置,并且还不知道引用数量的上线,无法解决循环引用无法回收的问题
- 按照上文的引用计数策略,它们的引用数量都是 2,但是,在函数 test 执行完成之后,对象 A 和 B 是要被清理的,但使用引用计数则不会被清理,因为它们的引用数量不会变成 0,假如此函数在程序中被多次调用,那么就会造成大量的内存不会被释放
1 | function test() { |
Major-Collection 分代 (新老生代) 回收法(V8)
新生代:存活时间较短的对象,简单说就是新产生的对象,容量只支持 1-8M
老生代:存活时间较长的对象,简单说就是经历过新生代垃圾回收后存活下来的对象,容量较大
Young-GC 新生代垃圾回收
- 新加入的对象会放入到使用区,当使用区快写满时,执行一次垃圾清理操作
- 对活动对象进行标记,完成后将适用对象复制到空闲区进行排序,非活动对象占用的空间清理掉。空间互换:使用区–> 空闲区、空闲区–> 使用区
- 当一个对象多次复制后依然存活,被认为是生命周期较长的对象,(如果一个对象占用空间超过 25%,会被直接移到老生代空间)随后移到老生代区,进行老生代的垃圾回收策略
Old-GC 老生代垃圾回收
整体采用标记清除法
Advantage 优点
分代式机制把一些新、小、存活时间短的对象作为新生代,采用一小块内存频率较高的快速清理,而一些大、老、存活时间长的对象作为老生代,使其很少接受检查,新老生代的回收机制及频率是不同的,可以说此机制的出现很大程度提高了垃圾回收机制的效率