垃圾回收机制(GC)

301 阅读4分钟

垃圾回收策略

1. 标记清除法

  • 先根据可达性算法标记出相应的可回收对象
  • 对可回收对象进行回收
缺点: 存在内存碎片,浪费空间

内存碎片.image

2. 标记整理法

  • 先根据可达性算法标记出相应的可回收对象
  • 对可回收对象进行回收
  • 将所有的存活对象都往一端移动,紧邻排列(解决了内存碎片的问题)
缺点: 每进行一次垃圾清除都要频繁地移动存活对象,效率十分低下

内存碎片排序.png

3. 引用计数清零

缺点: 循环引用,无法清除

4. V8对GC的优化: 分代式垃圾回收

  • 新生代垃圾回收: 复制算法,并行回收
  • 老生代垃圾回收: 标记整理,增量标记,惰性清理,并行回收,并发回收

分代式垃圾回收

将堆内存分为新生代和老生代两区域,采用不同的垃圾回收器也就是不同的策略管理垃圾回收

1. 新生代垃圾回收

  • 通常只支持 1~8M 的容量
  • 通过 Scavenge 的算法进行垃圾回收,主要采用一种复制式的方法(Cheney算法) Cheney算法: 将堆内存分为使用区和空闲区
    • 新加入的对象都会存放到使用区,当使用区快被写满时,就需要执行一次垃圾清理操作
    • 对使用区中的活动对象做标记,标记完成之后将使用区的活动对象复制进空闲区并进行排序
    • 将非活动对象占用的空间清理掉
    • 最后把原来的使用区变成空闲区,把原来的空闲区变成使用区
  • 当一个对象经过多次复制后依然存活,就会被移动到老生代中管理
  • 如果复制一个对象到空闲区时,空闲区空间占用超过了 25%,那么这个对象会被直接晋升到老生代中
  • 并行回收

2. 老生代垃圾回收

  • 标记整理法
  • 增量标记
  • 惰性清理
  • 并行回收
  • 并发回收

并行回收

多线程进行垃圾回收,减少占用主线程的运行时间

增量标记与惰性清理

1. 增量标记

将一次 GC 标记的过程,分成很多小步,每执行完一小步就让应用程序执行一会儿
  • 三色标记法: 解决暂停与恢复问题

    // 白色后面是白色|没有
    白色指的是未被标记的对象
    // 灰色后面是白色|没有
    灰色指自身被标记,成员变量(该对象的引用对象)未被标记
    // 黑色后面是黑色|灰色|没有
    黑色指自身和成员变量皆被标记
    

    image.png

  • 写屏障: 解决增量中修改引用问题

    // 黑色本来引用灰色对象,修改为引用白色对象
    一旦有黑色对象引用白色对象,该机制会强制将引用的白色对象改为灰色,从而保证下一次增量 GC 标记阶段可以正确标记,这个机制也被称作 强三色不变性
    

2. 惰性清理

假如当前的可用内存足以让我们快速的执行代码,可以将清理过程稍微延迟一下,可以按需逐一进行清理直到所有的非活动对象内存都清理完毕,后面再接着执行增量标记

并发回收

主线程在执行 JavaScript 的过程中,辅助线程能够在后台完成执行垃圾回收的操作,从而不占用主线程的运行

内存泄漏

不是所有无用对象内存都可以被回收的,那当不再用到的内存,没有及时回收时,我们叫它 内存泄漏

常见的内存泄露案例

  1. 全局变量-照成内存泄露: 范例中会把 name 定义到全局中
function fn() {
  // this.name = "你我贷"
  name = "你我贷"
}
console.log(name)
  1. 未销毁的定时器和回调函数-照成内存泄露
  2. 闭包-照成内存泄露
  3. DOM引用-照成内存泄露
var elements = {
  txt: document.getElementById("test")
}
function fn() {
  elements.txt.innerHTML = "1111"
}
function removeTxt() {
  document.body.removeChild(document.getElementById('test'))
}
fn() // 执行完后引用了id为test的元素
removeTxt() // 导致此时无法彻底清除

来源