后端 GC算法 day 5 | 青训营笔记

99 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天

今天学习的内容为垃圾回收算法,与JVM中的实践

垃圾回收算法

自动内存管理的目标

  • 为新对象分配空间
  • 找到存活对象
  • 回收死亡对象的内存空间

评价标准

  • 安全性(基本要求): 不能回收存活的对象
  • 吞吐率(GC占用的运行时间比例): 1 - GC时间/程序执行总时间
  • 暂停时间(单次GC对业务的影响): stop the word, GC标记时, 暂停业务线程的时间
  • 内存开销: GC算法在对象头标记与本身占用空间的开销

基本的GC算法思路

  • 追踪垃圾回收(Tracing grabage collection) 通常加上分代/分区思想
  • 引用计数(Reference counting)

深入理解JVM-垃圾收集器与分配策略

GC(Garbage Collection)技术在60s Lisp实现中就有应用

  1. 那些内存需要回收
  2. 什么时候回收
  3. 如何回收

确认对象已死:

  1. 引用计数 (无法解决循环引用
  2. 可达性分析(Reachability Analysis) 如果对象从GC Root出发无法访问到则可以回收 固定可以成为GC Root的对象:
    • 栈帧中对象
    • 方法区中类静态属性引用的对象(如类的静态变量)
    • 方法区中常量引用的对象(如字符串常量池String Table中的引用)
    • 本地方法栈中JNI引用对象
    • JVM内部引用(基本类型对应对象, 常驻异常对象)
    • 同步锁(synchronized)持有对象
    • JVM内部inspection相关(JMXBean, JVMTI) 不同垃圾回收器及当期回收内存区域不同, 会有其他对象临时加入, 如分代收集. 局部GC.
  3. Java引用类型
    1. 强引用 默认, 只要 gc root 到对象有引用存在, 就不会被gc
    2. 软引用 oom前会尝试回收
    3. 弱引用 gc时会直接回收
    4. 虚引用 不会对生命周期产生影响, 用于被回收时通知
  4. 生存还是死亡 对象被回收前要经历两次标记 1. 没有其他对象引用它, 2. finalize 不需要执行或以执行 (对象的finalize 方法只会被执行一次, 可以在此时被自救, (不建议)
  5. 回收方法区 类的常量和不再使用的类(类的回收需要类加载器已卸载) 使用反射,动态代理等字节码框架的场景, 需要回收(ZGC支持不完全)

垃圾收集算法:

主流虚拟机都是 Tracing GC, 基于可达性分析理论.

分代理论:

  1. 弱分代假说(Weak Generational Hypothesis): 绝大多数对象都是朝生夕灭的
  2. 强分代假说(Strong Generational Hypothesis): 熬过越多分代的对象越难消亡
  3. 跨代引用假说(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)

Pasted image 20230119113348.png

流程: 1. 标记出需要清除的对象 2. 统一回收标记的对象 缺点: 1. 执行效率不稳定 2. 内存空间碎片化严重

标记复制(Semispace Copy)

Pasted image 20230119113357.png 保留一部分区域, 每次清除时将存活对象移动到保留区域, 其他部分直接回收, 保留区域不一定要是1:1, Appel式回收中, 将新生代分为一块较大的 Eden 空间和两块较小的 Survivor, 保留一块 Survivor , 回收时将存活对象拷贝到 Survivor 上 (假如出现保留区域不够使用情况时, 还需要借用老年代

标记整理(Mark-Compact)

Pasted image 20230119113404.png

回收完成后, 将存活对象都移动到内存空间一侧 可以解决内存碎片问题, 但整理时需要暂停用户线程 与标记清除结合, 当内存碎片过多时整理一次