知识体系
- 定义垃圾数据
- 垃圾回收策略
- 垃圾回收机制
- 栈垃圾回收
- 堆垃圾回收
- 代际假说(The Generational Hypothesis)
- 分代收集
- 堆垃圾回收工作流程
- 主/副垃圾回收器 - 堆垃圾
- 全停顿 - 堆垃圾
垃圾数据
有些数据被使用之后,可能就不再需要了,这些数据被称为【垃圾数据】。
垃圾数据或占用内存,需要回收进行内存释放
垃圾回收策略
- 手动回收(C/C++)- 代码控制
- 自动回收(JS/Java/Python)
垃圾回收机制
栈垃圾的回收
ESP - 记录当前执行状态的指针
上代码
function foo(){
var a = 1
var b = {name:" 极客邦 "}
function showName(){
var c = " 极客时间 "
var d = {name:" 极客时间 "}
}
showName()
}
foo()
【如果】执行到showName函数时,JS引擎会创建showName函数的执行上下文,并将其压入栈中。同时还有一个ESP指向这个执行上下文,表示JS引擎正在执行这个函数。
【如果】showName执行完成,ESP会下移到foo函数的执行上下文,同时伴随着showName执行上下文的销毁。
堆垃圾的回收
当栈中的数据被回收后,此时堆垃圾中的对象数据仍然占用着空间
此时,就需要用到JS中的垃圾回收器
代际假说
V8实现垃圾回收的基础
- 两个特点
- 大部分对象一经分配内存,很快便不可访问
- 不死对象
分代收集
- 两个区域
- 新生代
- 生存时间短的对象
- 容量:1M ~ 8M
- 启用副垃圾回收器
- 老生代
- 生存时间长的对象
- 容量大
- 启用主垃圾回收器
- 新生代
垃圾回收工作流程 - 堆垃圾
标记空间中的 活动/非活动 对象回收非活动 对象占据的空间内存整理。整理内存碎片(不连续的内存空间)【副垃圾回收器不会产生内存碎片】
主/副垃圾回收器 - 堆垃圾
副垃圾回收器
- 负责新生区的垃圾回收。
- 大多数小的对象都会分配到新生区。区域不大,垃圾回收比较频繁
- 使用
Scavenge算法处理- 把新生区对半划分(空闲区域/对象区域)
1. 新加入的对象被分配到 [对象区域]
2. 对象区域被写满时 => 执行垃圾回收操作
【首先】标记 [对象区域] 中的垃圾
【然后】复制 存活的对象到 [空闲区域] (有序排列 => 相当于内存整理)
【最后】[对象区域] 与 [空闲区域] 角色翻转。
完成垃圾对象的回收
【无限重复使用】
【由于】复制操作需要时间成本
【所以】新生区空间设置比较小
【但是】空间小容易填满
【所以】JS引擎采用 【对象晋升策略】 => 经过两次垃圾回收还存活的对象,会被移到老生区
主垃圾回收器
-
负责老生区的垃圾回收
- 存储大对象数据
- 存储新生区晋升过来的数据
-
使用
标记-清除算法(Mark-Sweep)处理- 标记过程
遍历调用栈,跟堆中的地址进行对比. 【栈中存在,堆中也存在】活动对象 【栈中不存在,堆中存在】垃圾数据,进行标记-
清除过程
【产生内存碎片】
- 使用
标记-整理算法(Mark-Component)处理- 标记过程跟 标记-清除算法一样
- 后续不直接回收垃圾数据,而是让所有存活对象移向一端,然后清理掉垃圾数据。
全停顿 - 堆垃圾
垃圾回收运行在主线程上。进行垃圾回收时,会暂停JS脚本的运行;垃圾回收结束后再恢复脚本的执行。这个行为叫全停顿(Stop-The-World)
【提出问题】
- 主线程被垃圾回收占用太久,会影响JS脚本的运行,可能会造成页面卡顿。
【V8提出改进方案】
增量标记(Incremental Marking)- 将标记过程分为一个个的子标记,同时跟JS脚本交替运行,直到标记过程完成。
- 将标记过程分为一个个的子标记,同时跟JS脚本交替运行,直到标记过程完成。
外部参考:
[垃圾回收:垃圾数据如何自动回收 | 浏览器工作原理与实践 (poetries.top)]
(https://blog.poetries.top/browser-working-principle/guide/part3/lesson13.html)