这是我参与「第五届青训营 」伴学笔记创作活动的第5天
自动内存管理(垃圾回收)
自动内存管理管理的是动态内存, 即在程序运行时根据需求动态分配的内存, 例如C中的malloc()
自动内存管理有程序语言的运行时系统管理动态内存, 避免程序员手动管理, 专注于实现业务逻辑, 并且保证内存使用的正确性喝安全性, 避免了c++中double free, use after free等问题
垃圾回收主要面临三个任务, 即为对象分配空间, 找到存活对象, 回收死亡对象的内存空间.
相关线程概念
Mutator, 业务线程, 分配新对象, 修改对象指向关系
Collector, GC线程, 找到存货对象, 回收死亡对象的内存空间, 执行内存的标记, 内存的回收等
Serial GC, 只有一个collector的算法, collector执行时, 会暂停mutator业务线程, 等待这唯一一个collector执行完毕再恢复mutator.
Parallel GC, 拥有多个collector的算法, 执行GC时, parallel GC也会暂停mutator的执行, 并且同时调用多个collector去执行GC操作, 等待GC操作完成, 再恢复mutator. 因为执行GC的collector数量多余Serial GC, 因此parallel gc的效率略高于serial gc
Concurrent GC, mutator和collector可以同时执行, GC进行时, 不必暂停mutator, 而是单单唤醒collector进行垃圾回收. 但是collectors必须感知到对象指向关系的改变, 即已经标记对象所指向的对象也必须被标记.
评价GC算法
- 安全性. 不能回收存活的对象, 是基本的要求.
- 吞吐率. 1-(GC消耗事件/程序总时间), 即花在GC上的时间越短越好.
- 暂停时间. 即某些算法会暂停业务线程mutator, stop the world. 暂停时间越短越好, 判断业务是否能够感知到暂停
- 内存开销, GC元数据开销
追踪垃圾回收
对象被回收的条件: 指针指向关系不可达的关系
追踪垃圾回收主要分为标记根对象, 标记可达对象, 清理不可达对象
其中, 根对象主要包含静态变量, 全局变量, 常量, 线程栈等
接着, 求指针指向关系的传递闭包, 即从根对象出发, 找到所有可达对象
最后, 清理所有不可达对象, 根据对象的生命周期, 使用不同的标记和清理策略
- 将存活对象复制到另外的内存空间(copying GC)
- 将死亡对象的内存标记为"可分配"(mark-sweep GC), 使用free-list管理, 分配时优先从free-list中进行分配
- 移动并整理存活对象(mark-compact GC), 原地整理, 即将存活对象移动至一处, 减少外部碎片
分代GC
分代假说, 假定很多对象在分配出来后很快就会不再使用.
每个对象都有年龄, 即经历过GC的次数
分代GC的目的是将年轻的对象和年老的对象放在堆中不同的区域, 制定不同的GC策略, 降低整体内存管理的开销.
-
年轻代
常规的对象分配, 由于分代假说的存在, 存活的对象非常少, 因此可以采用copying GC, 将存活对象复制至额外的空间, GC吞吐率会很高
-
老年代
对象趋向于一直或者, 进行复制操作开销较大, 推荐使用mark-sweep方式, 使用free-list管理空闲内存, 避免多次复制操作(当外部碎片较多的时候, 也可以使用mark-compact GC, 对存活对象进行压缩)
引用计数
- 每个对象都有个一个与之关联的引用数目
- 对象存活条件: 当且仅当引用数大于0
优点是内存管理操作平摊到程序执行过程中, 内存不需要了解runtime的实现细节(例如C++中智能指针)
引用计数缺点也较多
- 维护开销较大, 即需要通过原子操作保证引用计数操作的原子性和可见性
- 无法回收环形数据结构, 可以通过weak-reference等方法解决
- 内存开销增大, 每个对象需要引入额外的内存空间存储引用数目
- 回收内存时依旧可能引发暂停操作