这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天
自动内存管理
基本概念
动态内存
- 程序运行时根据需求动态分配的内存
- malloc( )
垃圾回收
- 程序运行时系统回收动态内存
- 常见错误
double-freeuse-after-free
自动内存管理的任务
- 为新对象分配内存空间
- 寻找程序还会使用的对象,即存活对象
- 回收死亡对象的内存空间
相关线程
Mutator
- 业务线程
- 主要工作:分配新对象,修改对象指向关系
Collector
- GC 线程
- 主要工作:不停执行GC代码,找到存活对象,回收死亡对象内存空间
理解垃圾回收
GC 算法
Serial GC
- 特点:会暂停 Mutator,只有一个GC线程Collector
- 工作流程:暂停 Mutator,启动一个Collector去执行GC,执行完毕之后再将被暂停的业务线程 Mutator 恢复
Parallel GC
- 特点:会暂停 Mutator,有多个GC线程 Collector
- 工作流程:暂停 Mutator,为每一个 Mutator 启动 一个GC线程 Collector,同时处理GC,效率高于 Serial GC 算法
Concurrent GC
- 特点:Mutator 和 Collector 同时执行
- 工作流程:不会暂停 Mutator,即一边执行 Mutator 一边执行 Collector,需要执行GC的时候唤醒GC线程 Collector,GC执行完毕之后,将Collector 休眠
- 原理:Collectors 必须能够感知到对象指向关系的改变,否则当 Mutator与 Collectors同时在执行的过程中,对象有新的指向关系时,GC 就会出错
GC 算法模型评估
- 安全性:不能回收存活对象,即程序还会使用的对象
- 吞吐率:
- 暂停时间:越短越好 - 应该尽量使得业务无感知
- 内存开销:越小越好 - 从Runtime中额外开辟一些内存空间做一些数据结构或者是进行内存标记等操作
分析垃圾回收机制
基于追踪的 GC 机制
追踪回收步骤
- 1.标记根对象
- 根对象一般包含以下几类
- 静态变量
- 全局变量
- 常量
- 线程栈
- 根对象一般包含以下几类
- 2.标记
- 从根对象出发,找到所有可以走到的对象,标记为是存活的
- 3.清理
- 清理不可达的对象,此时不可达的对象意味着是已经死亡的对象
- 清理过程涉及三种策略
- Copying GC
- Mark-sweep GC
- Mark-compact GC
清理策略分析
Copying GC
- 将存活对象复制到另外的内存空间
- 把存活着的对象复制到另一块新区域,复制完成后,原区域相当于是清理完毕
Mark-sweep GC
- 将死亡对象的内存标记为可分配
- 将可分配的内存空间用 freeList 管理起来,需要分配的时候直接去freeList 内寻找合适的内存进行分配
Mark-compact GC
- 移动并整理存活对象
- 把存活的对象原地整理压缩到内存首部,使其紧凑
基于分代的 GC 机制
分代 GC 原理
分代 GC 把内存区域分为年轻内存区和老年内存区
- 年轻内存区特点
- 常规的对象分配 对象刚分配出来就是在年轻区
- 存活对象是很少 Copying GC 策略代价会很低
- GC 吞吐率高
- 老年内存区特点
- 对象趋向于一直存活
- 反复复制开销较大 Copying GC 策略代价很高
- 推荐使用 Mark-sweep GC 策略
引用计数的 GC 机制
引用计数 原理
每一个对象都有一个与之关联的引用数目
引用计数大于0表明对象是存活着的
引用计数 优势
内存管理操作被平摊到程序执行过程 内存管理不需要了解runtime的实现细节,只需要维护对象的引用计数即可
引用计数 缺陷
- 由于可能会存在多个线程去操作我们的对象,因此在维护的时候我们需要通过原子操作保证对引用计数操作的原子性和可见性,因此造成的资源开销是较大的
- 无法回收环形数据结构
- 每个对象由于引入了额外的内存空间存储引用计数,带来一定的内存开销
- 回收内存时,当节点数量较多时,依然是有引发暂停的可能性