☪️ 垃圾收集器

吞佛童子2022年6月9日
  • Java
  • JVM
大约 9 分钟

☪️ 垃圾收集器

  • JDK 8 默认垃圾收集器 Parallel Scavenge(新生代)+ Parallel Old(老年代)
  • JDK 9 默认垃圾收集器 G1
  • 服务端开发常见组合就是 ParNew + CMS
  • 查看垃圾收集器命令:
    • java -XX:+PrintCommandLineFlags -version

img_1.png

1. 新生代 GC 器

新生代全部采用 标记 - 复制 垃圾收集算法进行 GC

1) Serial

  1. 特点
  • 单线程进行 GC
  • 进行 GC 时,用户线程全部暂停,STW
  • 可与 Serial Old | CMS 搭配
  1. 优点
  • 简单高效,无线程交互的开销
  • 内存占用小
  • 适合运行在 客户端 模式下
  1. 缺点
  • 单线程
  • 暂停用户线程

2) ParNew

  1. 特点
  • 多线程 进行 GC
  • GC 时,用户线程全部暂停,STW
  • 可与 Serial Old | CMS 搭配
  1. 优点
  • 多线程 GC,速度提高,降低 STW 时间
  • 适合运行在 服务端 模式下
  1. 缺点

img_2.png

3) Parallel Scavenge

  1. 特点
    • 目标: 达到一个可控制的吞吐量
    • 3 个重要参数:
      • 控制最大垃圾收集时间 -XX:MaxGCPauseMillis
        • 垃圾收集时间缩短是通过 降低吞吐量 + 减少新生代空间 换取的,并非越小越好
      • 设置吞吐量大小 -XX:GCTimeRatio
        • default = 99%
      • 自适应策略 -XX:UseAdaptiveSizePolicy
        • 开启这个参数后,就不需要手工指定新生代大小,Eden 与 Survivor 比例(-XX:SurvivorRatio)等细节
        • 只需要设置好 堆大小(-Xmx 最大堆),以及 控制最大垃圾收集时间 [-XX:MaxGCPauseMillis] | 设置吞吐量大小 [-XX:GCTimeRatio]
        • 虚拟机就会根据当前系统运行情况收集监控信息,动态调整这些参数以尽可能地达到我们设定的最大垃圾收集时间或吞吐量
    • 支持多线程并行收集
  2. 优点
    • 适合运行 后台运算等不需要太多用户交互的任务
  3. 缺点

吞吐量 = 运行用户代码时间 / (运行用户代码时间 + GC 时间)


2. 老年代 GC 器

老年代可采用 标记 - 清除 | 标记 - 整理 垃圾收集算法进行 GC

1) Serial Old

  1. 特点
    • 单线程 GC
    • 标记 - 整理
  2. 优点
    • 在客户端模式下,与 Serial 搭配使用
    • 作为 CMS 失败的后备预案
  3. 缺点

img_3.png

2) Parallel Old

  1. 特点
    • 支持多线程并行收集
    • 标记 - 整理
  2. 优点
    • 与 Parallel Scavenge 搭配使用
  3. 缺点

img_4.png

3) CMS

  1. 特点
    • 目标:获取最短停顿时间
    • 支持 GC 线程 & 用户线程 并发进行
    • 标记 - 清除
    • 并发标记采用 增量更新,同时 写屏障 保证多线程安全
  2. 优点
    • 响应速度快,适合交互性强的服务端
  3. 缺点
    • 面向并发设计的程序对处理器资源敏感,GC 线程占用部分线程,导致应用程序变慢
    • 并发收集过程中,用户线程会产生浮动垃圾,只能下次 GC 再清理
    • 并发收集过程中需预留部分内存空间供用户线程使用
      • JDK 6 时,CMS 启动阈值 default = 92%
      • 若在运行期间预留的内存不足以满足分配新对象的需要,会发生 “并发失败”
      • 此时,CMS 采用后备预案 - 暂停所有用户线程,使用 Serial Old 进行老年代的垃圾收集,停顿时间更长
    • 标记 - 清除 会产生碎片空间,但无法分配大对象时,会提前触发 Full GC
  4. 过程
    • 初始标记:STW,标记 GC Roots 直接引用的对象
    • 并发标记:从 GC Roots 直接引用的对象 开始遍历整个对象图的过程
    • 重新标记:STW,增量更新 + 写屏障
    • 并发清除:清理掉死亡对象
  5. 参数
    • -XX:CMSInitiatingOccupancyFraction
      • 内存容量达到多少百分比触发 GC
      • default = 68%
      • JDK 6default = 92%
    • -XX:+UseCMSCompactAtFullGCCollection
      • Full GC 时开启内存整理功能
      • default = 开启
      • JDK 9 废弃
    • -XX:CMSFullGCsBdroreCompaction
      • 隔多少次 Full GC 进行一次内存整理的 Full GC
      • default = 0
      • JDK 9 废弃

img_5.png


3. 通用 GC 器

1) G1

  1. 特点
    • 目标
      • 在延迟可控的情况下,尽可能追求高吞吐量
    • 支持 GC 线程 & 用户线程 并发 进行
    • 整体 标记 - 整理,局部[2 个 Region间] 标记 - 复制
    • 面向局部收集
      • Region 作为回收单元,避免进行全堆的回收
      • 通过计算每个 Region 的回收价值,维护优先级列表,优先回收性价比高的 Region
    • 基于 Region 的堆内存布局
      • 将连续的 Java 堆分为多个大小相等的 Region,每个 Region 根据需要,扮演 Eden Survivor 老年代
      • Humongous 区域专门用于存放大小超过一个 Region 容量一半的 大对象
      • Region 大小可通过参数 -XX:G1HeapRegionSize 设置,范围 [1MB, 32MB] & 为 2 的整数次幂,default = 2048 * Region
    • 建立了停顿预测模型
      • 通过参数-XX:MaxGCPauseMillis指定停顿时间 default = 200ms
      • 以 “衰减均值” 为理论基础,通过比较每个 Region 的统计状态决定该 Region 是否值得回收
      • 衰减平均 是一种数学方法,用来计算一个数列平均值,给近期的数据更高的权重,强调近期数据对结果的影响
    • 并发标记
      • 原始快照 [SATB]
      • 用户线程新对象内存分配问题:
        • 每个 Region 维护 2 个 TAMS 指针,将 Region 中的一部分空间划分出来用于并发阶段的内存分配
        • 在 TAMS 指针之间的内存区域不纳入回收范围
        • 若内存回收速度赶不上内存分配速度,则也会出现 “并发失败” 问题,也需要暂停所有用户线程,进入 Full GC
    • 分代空间分配
      • 仍然存在 新生代 & 老年代;新生代 仍然分为 Eden & Survivor
      • 若设置了 -Xmn | G1NewSizePercent | G1MaxNewSizePercent,根据这个确定新生代大小;否则,继续往下判断
        • -Xmn 等同于 G1NexSizePercent == G1MaxNewSizePercent
      • 若设置了 -XX:NewRatio,根据此确定新生代大小;否则,继续往下判断
      • 若只设置了 G1NewSizePercent | G1MaxNewSizePercent,另一个根据默认值确定;否则,继续往下判断
        • G1NewSizePercent(default) = 5%
        • G1MaxNewSizePercent(default) = 60%
      • 若设置了 -XX:SurvivorRatio,据此设置;否则 default = 8
      • YGC
        • 只收集新生代
      • Mixed GC
        • 新生代 & 老年代 均收集
        • 老年代 Region 达到整个堆内存的 45% 时触发Mixed GC;-XX:InitiatingHeapOccupancyPercent 参数可设置阈值
  2. 优点
    • 无内存碎片,有利于程序长时间运行
  3. 缺点
    • 卡表更加复杂,内存占用更高
    • 运行时的额外负载高
  4. 过程
    • 初始标记:STW,标记与 GC Roots 直接关联的对象
    • 并发标记:从与 GC Roots 直接关联的对象进行可达性分析,遍历整个对象图
    • 最终标记:STW,原始快照 + 写屏障
    • 筛选回收:STW,更新 Region 统计数据,根据回收价值 & 成本进行排序,确定回收哪些 Region,将回收 Region 中的存活对象复制到 空 Region 区,清理掉旧 Region
  5. 细节问题补充
    • 2 个 Set
      • RSet 记录了其他 Region 中的对象引用本 Region 中对象的关系
      • Cset 是一次GC中需要被清理的 Region 集合
    • 2 种 GC
      • Young GCCSet 就是所有年轻代里面的 Region
      • Mixed GCCSet 是所有年轻代里的 Region + 并发标记阶段标记出来的收益高的 Region

img_6.png

img_7.png

2) Shenandoah

  1. 特点
    • 支持并发清理算法
    • 默认不分代收集
    • 摒弃记忆集,改为连接矩阵记录跨 Region 引用关系
  2. 优点
  3. 缺点
  4. 过程
    • 初始标记:STW,标记与 GC Roots 直接关联的对象,这里停顿时间只与 GC Roots 数量有关,与堆大小无关
    • 并发标记:从与 GC Roots 直接关联的对象进行可达性分析,遍历整个对象图
    • 最终标记:STW,原始快照 + 写屏障,并选出回收价值最高的 Region,构成回收集
    • 并发清理:清理整个 Region 内一个存货对象都没有的 Region
    • 并发回收:[核心差异] 将回收集中的存活对象赋值到未被使用的 Region 中,借助 读屏障 & 转发指针 实现并发下的移动对象指针
    • 初始引用更新:STW,未进行实际操作,只是保证所有 GC 线程已完成前面步骤
    • 并发引用更新:将堆中所有指向旧对象的引用修正到复制后的新地址
    • 最终引用更新:STW,修正存在于 GC Roots 的引用
    • 并发清理:整个回收集中已无存货对象,进行清理

3) ZGC

  1. 特点
    • 基于 Region 内存布局
      • 小型 Region : 固定为 2MB,存放 < 256KB 的对象
      • 中型 Region : 固定为 32MB,存放 < 4MB 的对象
      • 大型 Region : 不固定,可动态变化,但必须为 2MB 的整数倍,存放 > 4MB 的大对象
        • 每个 大 Region 只存放一个大对象
        • 在 ZGC 中不会被重分配
    • 不设置分代
    • 使用 读屏障染色指针内存多重映射等技术
      • 染色指针:
        • 将标记信息直接打在引用对象的指针上,将高 4 位提取出来存储 4 个标志信息
        • Finalizable - 能否被访问到
        • Remapped - 是否进入重分配集,即是否被移动过
        • Marked 1 + Marked 0 - 存放三色标记状态
    • 实现了可并发的 标记 - 整理 算法
    • 目标:低延迟
  2. 优点
  3. 缺点
  4. 过程
    • 初始标记:
    • 并发标记:
    • 最终标记:STW,读屏障
    • 并发预备重分配:统计本次收集要清理哪些 Region 区域,组成 重分配集
    • 并发重分配:[核心阶段] 将重分配集中的存货对象复制到新的 Region 中,并未重分配集中的每个 Region 维护转发表,记录从旧对象到新对象的引用关系
    • 并发重映射:修正整个堆中指向重分配集中旧对象的引用,由于即使不修正也可以进行自愈,因此不是很迫切,可以合并到下次 GC 的并发标记阶段完成,节省一次遍历对象图的开销
上次编辑于: 2022/10/10 下午8:43:48
贡献者: liuxianzhishou