1.垃圾回收的机制
垃圾回收算法
可访问性(reachability)算法
- 通过GCRoot 标记活动和非活动对象
- 从GC Root 顶节点开始去递归整颗内存树
- GC Root 能到达的是 标记为可访问对象,活动对象
- GC Root 不能到达的是 标记为不可访问对象,非活动
- 内存整理,由于内存存在大量不连续的空间(内存碎片),需要重新整理才能正常使用大空间
代际假说
- 大部分活动的对象都是 创建的快销毁的快,如果函数内定义的变量,块级作用域变量等
- 不死的对象会活的更久,window,API
垃圾回收器
- 主垃圾回收
- 老生代
- 更大的数据
- 不死的
- Major GC
- 标记-清除 mark-sweep算法
- 标记
- 从一个根节点开始递归遍历,能到达的对象就是活动对象,没到达就是垃圾数据
- 清除
- 把标记为垃圾的全部遍历清理
- 缺点:容易产生不连续的碎片
- 标记
- 标记-整理 mark compact算法
- 标记
- 从一个根节点开始递归遍历,能到达的对象就是活动对象,没到达就是垃圾数据
- 整理+清除
- 让活动对象移动到另外一端,然后直接清空掉另外一段以外的所有内存
- 标记
- 老生代
- 副垃圾回收
- 新生代
- 1-8m的
- 活动的,函数或块级作用域的变量
- Minor GC (Scavenger)
- scavenge算法
- 对象区域
- 空闲区域
- 流程
- 当对象区域快满的时候,先标记垃圾数据
- 把活动对象复制到空闲区域,同时顺便进行了排序,所以粘贴后是有序的,复制完把当前活动区直接清空
- 把空闲区域和对象区域 做互换,互换后就把刚刚整理好了活动对象放在了对象区域
- 对象晋升策略
- 如果连续两次垃圾回收依然存活,对象就被移到老生区中
- 新生代
2.优化垃圾回收
主要针对主垃圾回收器的效率,副垃圾回收器空间占用不大,可以忽略
早期的垃圾回收
全停顿stop the world
- 在主线程执行,造成阻塞
- 由于标记,整理十分耗时,进行垃圾回收大内存时候,会出现严重的卡顿
现代的垃圾回收器
- 并行回收
- 使用多个辅助线程执行一个完整的垃圾回收,标记和整理
- 缺点:必须全停顿,主线程先停止,才能回收
- 增量式垃圾回收
- 把一个大的回收任务里的标记任务拆解成多个小任务,并在浏览器空闲时执行一部分小任务
- 优点
- 可以随时被打断,和重新继续执行
- 难点
- 暂停需要保存当前的扫描结果,等待下一次继续执行
- 当有js代码改变了回收的对象,回收器能正常识别
- 实现
- 三色标记法(黑色+白色+ 灰色方案)
- 默认所有节点白色
- 没有被访问到,可以被回收
- 当GC Root能到达标记为黑色
- 已标记完成,待回收
- GC Root 引用着,但是子节点还没被垃圾回收器标记处理,也表明目前正在处理这个节点
- 默认所有节点白色
- 判断逻辑
- 当没有灰色节点
- 没有,正面已经标记完成,可以回收
- 有灰色节点
- 下次继续从灰色做起始开始位置
- 当没有灰色节点
- 写屏障 (Write-barrier) 机制
- 把黑色指向白色的引用 -> 改为黑色指向灰色
- 强三色不变性
- 三色标记法(黑色+白色+ 灰色方案)
- 流程图 www.processon.com/diagraming/…
- 并发回收
- 通过辅助线程在后台执行回收动作
- 主线程在执行 JavaScript 的过程中,辅助线程能够在后台完成执行垃圾回收的操作。
- 难点
- 由于是同时操作,对象的变化可能导致的标记好的逻辑全部失效
- 由于同时操作,会出现跟数据库一样的同时读写问题
- concurrent
- v8实际使用的方案
- 使用并发利用辅助线程进行标记
- 使用并行进行清理和整理
- 使用增量进行清理的任务,穿插着浏览器空闲时
3.常见的内存问题
1. 内存泄露(memory leak)
- 已经不再需要使用的数据,没有被正常释放
- 是一个缓慢上传过程
- 避免定义了全局的变量,如在方法里,定义了全部变量
function foo() {
//创建一个临时的temp_array
this.temp_array = new Array(200000)
/**
* this.temp_array
*/
}
//使用严格模式 use strict解决
- 防止闭包的引用的数据
function fun(){
var obj = new Object()
obj.arr = new Array(200000)
obj.info = 'xxx'
return showInfo(){
return obj.info
}
}
let myShowInfo = fun()
myShowInfo()
//应该把大对象改为小对象
obj.info = 'xxx'
let info = obj.info
return showInfo(){
return info
}
//虽然也产生闭包,但是闭包的大小只是字符串变量
- 游离dom
// 动态在js创建的dom,如果被js的变量引用则无法释放
var ul;
function createNode() {
var ul = document.createElement('ul');
document.body.append(ul)
}
createNode()
//这里的全局ul会一直指向ul,导致即使removeChild了ul ,也引入被全局window.ul引用
2. 内存膨胀(memory bloat)
- 是一个快速上升的过程,然后达到一个平衡,或下降的状态
- 可能是代码里面没有正确的创建使用内存,导致瞬间飙升
3. 频繁的垃圾回收
大量的临时变量重复创建销毁,多复用变量,常用的变量可提升到全局常用
function foo() {
for (let i = 0; i < 10000; i++){
let a = new Array(1000)
}
}
foo()