GC
2023年4月8日大约 3 分钟
进程虚拟地址空间
- Code Segment 代码段(程序要执行的指令)
- Data Segment 数据段(全局变量、静态数据)
- Heap 堆(需要程序手动释放)(c\c++ 手动垃圾回收容易出现 悬挂指针-释放早了、内存泄漏-忘了释放)
- Stack 栈(函数局部变量、参数和返回值)函数调用完成后销毁(随着函数调用栈的销毁而释放内存)
栈、Data Segment 数据段上的对象作为root
基于它们 2个追踪
能追踪到的数据就代表是存活有引用的数据
不能编译期间确定大小(append slice)生命周期超出该函数(返回 *int) 不适合分配栈上(内存逃逸)
程序中用得到的数据一定是栈、数据段可以追踪到的数据,追踪不到也就意味着用不上
主流垃圾回收算法 : 数据 “可达性” 近似等于 “存活性”
- 标记-清扫算法 核心思想
追踪数据,能追踪到的进行标记
追踪不到的就是垃圾
- 三色抽象 (heap \ stack \ data segment) 白色\灰色\黑色
a. 刚开始数据都是白色
b. 直接追踪到的root节点标记为灰色(当前节点展开追踪还未完成)
c. 节点追踪完成后标记为黑色
结论一、没有灰色时候表示追踪已经完成
结论二、回收所有白色对象的内存
结论三、黑色表明追踪完成,无需再追踪,是存活数据
什么情况下会出现存活数据误判为垃圾
当存活数据(白色)在与黑色对象关联,而黑色对象是标记完成的不会再做标记,白色就一直是白色就会被清除
所以白色对象不能被黑色引用 (读写屏障)
强弱三色不变式 (严禁白色被黑色引用、白色可以被灰色引用)
标记清扫容易造成内存碎片化 - 大量不连续的小分块内存 - 涉及内存使用率
1. 按内存规格分类排列
2. 移动内存数据
标记 - 整理算法(标记后移动非垃圾数据)(扫描移动开销)
分代回收(新生代、老年代)老年代对象经历多次GC依然存活是更有生命力的 (降低对老年代对象GC的频率提升GC效率)
引用计数式回收
- 引用计数表示一个对象被引用的次数(计数为0时候表示可以被回收)
引用计数垃圾识别的任务分摊到每一次对数据对象的操作之中
高频率更新引用计数带来开销
循环引用带来内存泄漏
增量式垃圾回收 - 停止程序一小段时间清理垃圾再执行程序
说一说个人对Go垃圾回收的理解
第一就是程序执行的内存之中大概分成四种:代码段、数据段、栈、堆;其中需要清理的是堆数据里面的垃圾数据
那么在堆里面什么样的数据认为是垃圾数据呢,就是没有被栈和数据段引用数据 - 就是垃圾数据
所以从栈和数据段开始追踪扫描,但凡是追踪不到的就是垃圾数据 (标记 - 清扫算法核心思想)
golang的也是这种标记清扫的思想
同时golang又实用了三色抽象去清扫垃圾
第一将所有内存标记为白色
第二将所有追踪到的数据标记为灰色
第三对所有灰色追踪灰色能追踪到的数据
第四所有追踪完下属数据的标记为黑色
最后白色的数据就是无法追踪到的数据
白色的数据进行清除
缺点是会产生内存碎片,衍生出了标记压缩、复制等方式处理内存碎片