垃圾回收策略
1. 标记清除法
- 先根据可达性算法标记出相应的可回收对象
- 对可回收对象进行回收
缺点: 存在内存碎片,浪费空间
2. 标记整理法
- 先根据可达性算法标记出相应的可回收对象
- 对可回收对象进行回收
- 将所有的存活对象都往一端移动,紧邻排列(解决了内存碎片的问题)
缺点: 每进行一次垃圾清除都要频繁地移动存活对象,效率十分低下
3. 引用计数清零
缺点: 循环引用,无法清除
4. V8对GC的优化: 分代式垃圾回收
- 新生代垃圾回收: 复制算法,并行回收
- 老生代垃圾回收: 标记整理,增量标记,惰性清理,并行回收,并发回收
分代式垃圾回收
将堆内存分为新生代和老生代两区域,采用不同的垃圾回收器也就是不同的策略管理垃圾回收
1. 新生代垃圾回收
- 通常只支持 1~8M 的容量
- 通过 Scavenge 的算法进行垃圾回收,主要采用一种复制式的方法(Cheney算法)
Cheney算法: 将堆内存分为使用区和空闲区
- 新加入的对象都会存放到使用区,当使用区快被写满时,就需要执行一次垃圾清理操作
- 对使用区中的活动对象做标记,标记完成之后将使用区的活动对象复制进空闲区并进行排序
- 将非活动对象占用的空间清理掉
- 最后把原来的使用区变成空闲区,把原来的空闲区变成使用区
- 当一个对象经过多次复制后依然存活,就会被移动到老生代中管理
- 如果复制一个对象到空闲区时,空闲区空间占用超过了 25%,那么这个对象会被直接晋升到老生代中
- 并行回收
2. 老生代垃圾回收
- 标记整理法
- 增量标记
- 惰性清理
- 并行回收
- 并发回收
并行回收
多线程进行垃圾回收,减少占用主线程的运行时间
增量标记与惰性清理
1. 增量标记
将一次 GC 标记的过程,分成很多小步,每执行完一小步就让应用程序执行一会儿
-
三色标记法: 解决暂停与恢复问题
// 白色后面是白色|没有 白色指的是未被标记的对象 // 灰色后面是白色|没有 灰色指自身被标记,成员变量(该对象的引用对象)未被标记 // 黑色后面是黑色|灰色|没有 黑色指自身和成员变量皆被标记 -
写屏障: 解决增量中修改引用问题
// 黑色本来引用灰色对象,修改为引用白色对象 一旦有黑色对象引用白色对象,该机制会强制将引用的白色对象改为灰色,从而保证下一次增量 GC 标记阶段可以正确标记,这个机制也被称作 强三色不变性
2. 惰性清理
假如当前的可用内存足以让我们快速的执行代码,可以将清理过程稍微延迟一下,可以按需逐一进行清理直到所有的非活动对象内存都清理完毕,后面再接着执行增量标记
并发回收
主线程在执行 JavaScript 的过程中,辅助线程能够在后台完成执行垃圾回收的操作,从而不占用主线程的运行
内存泄漏
不是所有无用对象内存都可以被回收的,那当不再用到的内存,没有及时回收时,我们叫它 内存泄漏
常见的内存泄露案例
- 全局变量-照成内存泄露: 范例中会把 name 定义到全局中
function fn() {
// this.name = "你我贷"
name = "你我贷"
}
console.log(name)
- 未销毁的定时器和回调函数-照成内存泄露
- 闭包-照成内存泄露
- 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() // 导致此时无法彻底清除