这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天
今天学习的内容为垃圾回收算法,与JVM中的实践
垃圾回收算法
自动内存管理的目标
- 为新对象分配空间
- 找到存活对象
- 回收死亡对象的内存空间
评价标准
- 安全性(基本要求): 不能回收存活的对象
- 吞吐率(GC占用的运行时间比例): 1 - GC时间/程序执行总时间
- 暂停时间(单次GC对业务的影响): stop the word, GC标记时, 暂停业务线程的时间
- 内存开销: GC算法在对象头标记与本身占用空间的开销
基本的GC算法思路
- 追踪垃圾回收(Tracing grabage collection) 通常加上分代/分区思想
- 引用计数(Reference counting)
深入理解JVM-垃圾收集器与分配策略
GC(Garbage Collection)技术在60s Lisp实现中就有应用
- 那些内存需要回收
- 什么时候回收
- 如何回收
确认对象已死:
- 引用计数 (无法解决循环引用
- 可达性分析(Reachability Analysis)
如果对象从GC Root出发无法访问到则可以回收
固定可以成为GC Root的对象:
- 栈帧中对象
- 方法区中类静态属性引用的对象(如类的静态变量)
- 方法区中常量引用的对象(如字符串常量池String Table中的引用)
- 本地方法栈中JNI引用对象
- JVM内部引用(基本类型对应对象, 常驻异常对象)
- 同步锁(synchronized)持有对象
- JVM内部inspection相关(JMXBean, JVMTI) 不同垃圾回收器及当期回收内存区域不同, 会有其他对象临时加入, 如分代收集. 局部GC.
- Java引用类型
- 强引用 默认, 只要 gc root 到对象有引用存在, 就不会被gc
- 软引用 oom前会尝试回收
- 弱引用 gc时会直接回收
- 虚引用 不会对生命周期产生影响, 用于被回收时通知
- 生存还是死亡 对象被回收前要经历两次标记 1. 没有其他对象引用它, 2. finalize 不需要执行或以执行 (对象的finalize 方法只会被执行一次, 可以在此时被自救, (不建议)
- 回收方法区 类的常量和不再使用的类(类的回收需要类加载器已卸载) 使用反射,动态代理等字节码框架的场景, 需要回收(ZGC支持不完全)
垃圾收集算法:
主流虚拟机都是 Tracing GC, 基于可达性分析理论.
分代理论:
- 弱分代假说(Weak Generational Hypothesis): 绝大多数对象都是朝生夕灭的
- 强分代假说(Strong Generational Hypothesis): 熬过越多分代的对象越难消亡
- 跨代引用假说(Intergenerational Reference Hypothesis): 跨代引用相对于同代引用来说仅占极少数
将java堆分成不同区域 ⇒ 新生代 (young generation), 老生代 (old generation)
- Partial gc: 目标不是整个java堆
- minor gc / young gc , 只针对新生代
- major gc / old gc, 只针对老生代(CMS only), major gc 也有可能指代 full gc
- mixed gc 新生代和部分老生代 (G1 only)
- full gc 收集整个 java堆和方法区
标记清除(Mark-Sweep)
流程: 1. 标记出需要清除的对象 2. 统一回收标记的对象 缺点: 1. 执行效率不稳定 2. 内存空间碎片化严重
标记复制(Semispace Copy)
保留一部分区域, 每次清除时将存活对象移动到保留区域, 其他部分直接回收,
保留区域不一定要是1:1, Appel式回收中, 将新生代分为一块较大的 Eden 空间和两块较小的 Survivor, 保留一块 Survivor , 回收时将存活对象拷贝到 Survivor 上 (假如出现保留区域不够使用情况时, 还需要借用老年代
标记整理(Mark-Compact)
回收完成后, 将存活对象都移动到内存空间一侧 可以解决内存碎片问题, 但整理时需要暂停用户线程 与标记清除结合, 当内存碎片过多时整理一次