垃圾回收
一些高级语言自带GC(Garbage Collection)比如java,python,javascript等,有些语言没有比如c和c++等需要手动管理内存
可达性 (Reachability)
以某种方式可访问或者说可用的值,它们被保证存储在内存中,反之不可访问则需要被回收
最常见的两种算法
- 标记清除法
- 引用计数法
标记清除法
标记清除(Mark-Sweep),分为标记和清除两个阶段,算法流程大致如下:
- 垃圾收集器在运行时给内存中的所有变量加上一个标记,假设所有内存中所有对象都是垃圾,全部标记为0
- 然后从各个根对象开始遍历,把不是垃圾的节点改为1
- 清除所有标记为0的垃圾,销毁并回收他们所占用的内存空间
- 最后,把所有内存中对象标记修改为0,等待下一轮垃圾回收
优点
打标记无非打与不打两种情况,一位二进制就可以标记,非常简单
缺点
清楚之后,剩余的对象内存是不变的,也会导致空闲内存空间是不连续的,于是出现了内存碎片,这就牵扯出了内存分配的问题。综上所述,有两个明显的缺点
- 内存碎片化
- 分配速度慢
引用计数法
引用计数法(Reference Counting)
- 当声明了一个变量并且将一个引用类型赋值给该变量的时候这个值的引用次数就为1
- 如果同一个值又被赋给另一个变量,引用数+1
- 如果该变量的值被其他的值覆盖了,引用数-1
- 当这个值的引用次数变为0的时候,说明没有变量在使用,这个值就没法被访问了,回收空间,垃圾回收器会在运行的时候清理掉引用次数为0的值占用的内存
但是有个很严重的问题,那就是循环引用
function test() {
let a = new Object()
let b = new Object()
a.b = b
b.a = a
}在使用引用计数法时,这个函数执行完不会被清理,如果这个函数被多次调用,就会造成大量的内存不会被释放
相比较标记清楚法则不会出现此问题,所以放弃引用计数,选择标记清除法
分代式垃圾回收
V8的回收策略主要基于分代式垃圾回收,V8中将堆分为新生代和老生代两个区域
新生代的对象为存活时间较短的对象,简单来说就是新产生的对象,通常只支持1~8M的容量,而老生代的对象为存活时间较长或常驻内存的对象,简单老说就是经过新生代垃圾回收后存活下来的对象,容量通常比较大

新生代垃圾回收
一个名叫Scavenge的算法进行垃圾回收,主要采用了一个复制式的方法即Cheney算法
Cheney算法将堆内存一分为二,一个是处于使用状态的空间,一是出于使用状态的空间称为使用区,一是处于闲置状态的快捷称为空闲区

新加入的对象都会存放到使用区,当使用区块被写满时,就要执行一次垃圾清理操作
当开始进行垃圾回收时,新生代垃圾回收器会对使用区的活动对象做标记,标记完成之后将使用区的活动对象复制空闲区并进行排序,随后进入垃圾清理阶段,即将非活动对象占用的空间清理掉。最后进行角色互换,把原来的使用区变为空闲区,把元的空闲区变为使用区
当一个对象经过对此复制后依然存活,它将会被认为是生命周期较长的对象,随后会被移动到老生代中,采用老生代的垃圾回收策略进行管理
老生代垃圾回收
对于大多数占用空间大、存活时间长的对象会被分配到老生代里,因为老生代中对象通常较大,如果再如新生代一般分区然后复制来复制去就非常耗时,导致回收效率不高。所以整个流程采用的就是上文所说的标记清除法
全停顿
js是单线程的语言,在进行垃圾回收时会阻塞js脚本的执行,需要等待垃圾回收完毕后再恢复执行,这种行为叫全停顿(Stop-The-World)