JVM之垃圾回收

一、垃圾判断算法

1. 引用计数法

  • 规则:对象被引用 + 1,引用失效 - 1;计数为 0 判定为垃圾
  • 缺点:无法解决循环引用,JVM 不采用

2. 可达性分析算法(JVM 主流)

  • 核心:以 GC Roots 为起点向下遍历引用链
  • 未被引用链到达的对象 = 垃圾对象

常见 GC Roots

  1. 虚拟机栈中局部变量引用的对象
  2. 方法区静态变量、常量引用对象
  3. 本地方法栈 Native 引用对象
  4. 活跃线程、锁对象等

二、四种引用类型(用途 + 场景)

1. 强引用

  • 常态引用:Object obj = new Object()
  • 特性:永不回收,内存溢出也不释放
  • 场景:业务正常对象

2. 软引用 SoftReference

  • 特性:内存充足不回收,内存不足必回收
  • 场景:图片缓存、本地临时缓存(防 OOM)

3. 弱引用 WeakReference

  • 特性:只要 GC 触发,直接回收,不分内存是否充足
  • 场景:WeakHashMap、缓存、防止内存泄漏

4. 虚引用 PhantomReference

  • 特性:无任何引用作用,仅做回收前通知
  • 场景:堆外内存回收、资源释放监控

三、垃圾回收基础算法

1. 标记 - 清除

  • 流程:标记垃圾 → 统一清除
  • 缺点:产生内存碎片,大对象无法分配

2. 复制算法

  • 流程:存活对象复制到空闲区,原区域整体清空
  • 优点:无碎片、效率高
  • 缺点:内存空间折损
  • 适用:新生代(存活对象少)

3. 标记 - 整理

  • 流程:标记垃圾 → 存活对象向一端压缩整理
  • 优点:无内存碎片
  • 缺点:移动对象,开销大
  • 适用:老年代(存活对象多)

4. 分代收集

  • 核心思想:按对象存活周期划分内存
    • 新生代:对象朝生夕灭 → 复制算法
    • 老年代:对象长期存活 → 标记 - 整理 / 标记 - 清除

四、主流垃圾收集器(分代划分)

新生代收集器

  1. Serial:单线程、串行回收,客户端模式,STW 长
  2. ParNew:Serial 多线程版本,CMS 专属新生代搭档
  3. Parallel Scavenge:并行回收,高吞吐量优先

老年代收集器

  1. Serial Old:Serial 老年代版本,单线程标记整理
  2. Parallel Old:Parallel Scavenge 老年代版本,吞吐量优先
  3. CMS:并发标记清除,低延迟,主打并发,存在碎片

全堆收集器(不分代 / 混合)

  1. G1

    (JDK8 主流)

    • 分区 Region 管理,可预测停顿
    • 兼顾吞吐量 + 低延迟,全局混合回收
  2. ZGC / Shenandoah

    (JDK11+ 低延迟)

    • 超大堆、极低 STW,毫秒级停顿
    • 主打高并发、大内存、低延迟业务

五、内存分配与回收策略

  1. 对象优先在 Eden 分配

    绝大多数短期小对象,新生代 Eden 区诞生

  2. 大对象直接进入老年代

    长字符串、大数组,避免新生代大量复制开销

  3. 长期存活对象晋升老年代

    对象熬过多次 Minor GC,年龄阈值达标进入老年代

  4. 动态年龄判断、空间分配担保 作为补充规则

CMS 收集器(Concurrent Mark Sweep)

定位:老年代、低延迟、并发收集、标记 - 清除算法

核心:尽量和用户线程并发,减少 STW

CMS 四大阶段(2 次 STW)

  1. 初始标记(STW)
  • 只扫描 GC Roots 直接关联的老年代对象
  • 速度极快,短暂停顿
  1. 并发标记(并发,无 STW)
  • 用户线程正常运行
  • 顺着初始标记对象,完整遍历整个老年代引用链
  • 找出所有存活对象
  • 并发期间会产生浮动垃圾
  1. 重新标记(STW)
  • 修正并发标记阶段,因用户线程运行新增 / 修改引用产生的漏标
  • 停顿时间比初始标记长,远低于 Full GC
  1. 并发清除(并发,无 STW)
  • 并发清理死亡对象,标记 - 清除
  • 不移动对象,产生内存碎片

触发 Full GC 场景

  • 老年代空间不足,并发回收跟不上对象产生速度
  • 碎片过多,大对象无法分配
  • 显式调用 System.gc()

G1 收集器(Garbage-First)

定位:全堆回收、分区 Region 模型、可预测停顿、JDK8 默认主力

核心:化整为零、优先回收垃圾最多的区域、混合回收

关键前置:Region 分区

  • 把整个堆拆分为多个大小相等的 Region
  • 逻辑划分:Eden、Survivor、Old、Humongous(大对象区)
  • G1 不要求完整分代连续内存,灵活管理

G1 完整五大阶段(3 次 STW)

  1. 初始标记(STW)
  • 同 CMS:只标记 GC Roots 直达对象
  • 伴随一次普通 Minor GC,速度很快
  1. 并发标记(并发)
  • 和用户线程并发执行
  • 遍历全堆引用链,统计每个 Region 垃圾占比
  • 计算「回收收益」
  1. 最终标记(STW)
  • 修正并发阶段引用变动,处理漏标对象
  1. 筛选回收(STW,核心)
  • 根据停顿时间目标,优先选择垃圾最多的 Region
  • 批量回收、复制存活对象、压缩内存
  • G1 是复制 + 标记整理无内存碎片
  1. 混合回收
  • 不是单独阶段,是 G1 常态
  • 一次 GC:回收部分新生代 + 部分垃圾最多老年代 Region
  • 减少 Full GC 频率

G1 额外核心特点

  1. 算法:
  • 整体:分代收集
  • 局部:复制 + 标记整理 → 无碎片
  1. 核心优势
  • 可指定最大停顿时间 -XX:MaxGCPauseMillis
  • 大对象单独存 Humongous 区,避免老年代拷贝
  • 兼顾吞吐量 + 低延迟
  1. 适用场景:堆内存较大、需要可控停顿的业务

CMS vs G1 高频对比

维度 CMS G1
回收范围 只负责老年代 全堆统一管理
算法 标记 - 清除(碎片) 分区复制 + 整理(无碎片)
停顿控制 不可预测 可预设停顿时间
大对象 劣势 单独 Humongous 区,友好
碎片问题 严重 几乎没有
JDK 版本 JDK8 常用,逐步淘汰 JDK8 默认、JDK9 + 默认

ZGC

ZGC 核心定位

  • JDK11 推出、JDK15 正式默认
  • 超低延迟、TB 级大堆、几乎全程并发
  • 核心目标:STW 控制在 10ms 以内
  • 关键技术:染色指针(Colored Pointers)+ 读屏障
  • 算法:并发标记 + 并发转移整理,无内存碎片

核心前置:染色指针

64 位指针高几位存储标记状态,不占用堆内存:

  • Final:最终存活

  • Remapped:已重映射(整理后新地址)

  • Marked0 / Marked1:交替标记周期

    优势:

  1. 无需在对象头存标记位
  2. 全局随时、快速判定对象状态
  3. 实现并发标记、并发整理、并发销毁

染色指针结构

64 位 CPU 理论寻址:2^64 地址

ZGC 只用到低 42 位存真实内存地址。

不占用对象头、不占用堆内存,直接把 GC 状态打在「对象地址指针」上。

1
2
高22位  |  低42位
标记位 | 真实内存地址

Marked0 本轮存活标记

Marked1 下一轮存活标记(交替使用)

Remapped 已转移 / 重映射(对象搬家完成)

Finalizable 仅可被 finalize 回收

染色指针 核心工作机制

1. 地址 = 地址 + GC 状态

普通垃圾收集器:

  • 存活标记、转移状态 存在 对象头 / 位图

ZGC:

  • 不改对象、不占堆内存
  • 直接给地址 “上色”,指针自带 GC 状态
2. 并发标记阶段怎么用?

GC 遍历引用时:

  • 存活对象 → 把指针打上 Marked0 / Marked1 染色标记
  • 垃圾对象 → 不打标记

业务线程并发运行,只要读取引用,读屏障自动校验染色位,判断对象死活。

3. 并发整理(对象搬家)怎么用?

ZGC 最强:并发移动对象,不 STW

  1. 把旧 Region 里存活对象,复制到新 Region
  2. 新地址打上 Remapped(已重映射)
  3. 旧引用还指向旧地址(带旧染色标记)

读屏障拦截:

业务线程访问旧地址时:

自动识别「旧地址未重映射」→ 转发到新真实地址

全程业务无感知、不停机。


ZGC 完整 6 大阶段

仅 2 次短暂 STW,其余全并发

1. 初始标记(STW,极短)

  • 只扫描 GC Roots
  • 毫秒级停顿,只抓第一层引用
  • 唯一目的:开启本轮并发标记

2. 并发标记(全程并发)

  • 用户线程正常运行,无卡顿
  • 从 GC Roots 出发,遍历全堆引用链
  • 依靠染色指针标记存活对象
  • 全程和业务线程并发执行

3. 并发预备重分配(并发)

  • 统计各区域存活占比
  • 选出垃圾最多、回收收益最高的分区
  • 标记需要并发转移整理的区域

4. 重定位标记(STW,极短)

  • 短暂停顿
  • 完成剩余根引用重映射修正
  • 衔接下一阶段并发整理

5. 并发重分配 / 整理(核心)

  • ZGC 最强特性:并发压缩、并发移动对象
  • 将选中分区存活对象,复制到新空闲 Region
  • 旧区域直接回收
  • 通过读屏障:访问旧地址 → 自动转发到新地址
  • 全程业务不停、无长停顿

6. 并发重置(并发)

  • 清空本轮标记、重映射状态
  • 染色指针状态复位
  • 为下一次 GC 循环做准备

ZGC 三大关键技术

  1. 染色指针

    地址自带 GC 状态,节约堆内存,快速标记、转发地址。

  2. 读屏障 Load Barrier

    每次对象读取拦截,自动处理:标记更新、旧地址转发,保证并发移动安全。

  3. 分区域 + 动态回收

    同 G1 Region 思想,只回收垃圾多的区域,不用整堆全量回收。


ZGC 优缺点

优点

  1. 极低停顿:STW 固定 1~10ms,和堆大小无关
  2. 支持 TB 级超大堆
  3. 全程并发压缩,无内存碎片
  4. 吞吐量损耗可控,远优于 CMS
  5. 不分代设计(JDK21+ 引入分代 ZGC,进一步提升吞吐)

缺点

  1. 大量读屏障,轻微损耗 CPU
  2. JDK11 后才稳定,老项目不兼容

JVM之垃圾回收
http://hanqichuan.com/2019/07/23/jvm/JVM之垃圾回收/
作者
韩启川
发布于
2019年7月23日
许可协议