JVM之垃圾回收
一、垃圾判断算法
1. 引用计数法
- 规则:对象被引用 + 1,引用失效 - 1;计数为 0 判定为垃圾
- 缺点:无法解决循环引用,JVM 不采用
2. 可达性分析算法(JVM 主流)
- 核心:以 GC Roots 为起点向下遍历引用链
- 未被引用链到达的对象 = 垃圾对象
常见 GC Roots
- 虚拟机栈中局部变量引用的对象
- 方法区静态变量、常量引用对象
- 本地方法栈 Native 引用对象
- 活跃线程、锁对象等
二、四种引用类型(用途 + 场景)
1. 强引用
- 常态引用:
Object obj = new Object() - 特性:永不回收,内存溢出也不释放
- 场景:业务正常对象
2. 软引用 SoftReference
- 特性:内存充足不回收,内存不足必回收
- 场景:图片缓存、本地临时缓存(防 OOM)
3. 弱引用 WeakReference
- 特性:只要 GC 触发,直接回收,不分内存是否充足
- 场景:WeakHashMap、缓存、防止内存泄漏
4. 虚引用 PhantomReference
- 特性:无任何引用作用,仅做回收前通知
- 场景:堆外内存回收、资源释放监控
三、垃圾回收基础算法
1. 标记 - 清除
- 流程:标记垃圾 → 统一清除
- 缺点:产生内存碎片,大对象无法分配
2. 复制算法
- 流程:存活对象复制到空闲区,原区域整体清空
- 优点:无碎片、效率高
- 缺点:内存空间折损
- 适用:新生代(存活对象少)
3. 标记 - 整理
- 流程:标记垃圾 → 存活对象向一端压缩整理
- 优点:无内存碎片
- 缺点:移动对象,开销大
- 适用:老年代(存活对象多)
4. 分代收集
- 核心思想:按对象存活周期划分内存
- 新生代:对象朝生夕灭 → 复制算法
- 老年代:对象长期存活 → 标记 - 整理 / 标记 - 清除
四、主流垃圾收集器(分代划分)
新生代收集器
- Serial:单线程、串行回收,客户端模式,STW 长
- ParNew:Serial 多线程版本,CMS 专属新生代搭档
- Parallel Scavenge:并行回收,高吞吐量优先
老年代收集器
- Serial Old:Serial 老年代版本,单线程标记整理
- Parallel Old:Parallel Scavenge 老年代版本,吞吐量优先
- CMS:并发标记清除,低延迟,主打并发,存在碎片
全堆收集器(不分代 / 混合)
G1
(JDK8 主流)
- 分区 Region 管理,可预测停顿
- 兼顾吞吐量 + 低延迟,全局混合回收
ZGC / Shenandoah
(JDK11+ 低延迟)
- 超大堆、极低 STW,毫秒级停顿
- 主打高并发、大内存、低延迟业务
五、内存分配与回收策略
对象优先在 Eden 分配
绝大多数短期小对象,新生代 Eden 区诞生
大对象直接进入老年代
长字符串、大数组,避免新生代大量复制开销
长期存活对象晋升老年代
对象熬过多次 Minor GC,年龄阈值达标进入老年代
动态年龄判断、空间分配担保 作为补充规则
CMS 收集器(Concurrent Mark Sweep)
定位:老年代、低延迟、并发收集、标记 - 清除算法
核心:尽量和用户线程并发,减少 STW
CMS 四大阶段(2 次 STW)
- 初始标记(STW)
- 只扫描 GC Roots 直接关联的老年代对象
- 速度极快,短暂停顿
- 并发标记(并发,无 STW)
- 用户线程正常运行
- 顺着初始标记对象,完整遍历整个老年代引用链
- 找出所有存活对象
- 并发期间会产生浮动垃圾
- 重新标记(STW)
- 修正并发标记阶段,因用户线程运行新增 / 修改引用产生的漏标
- 停顿时间比初始标记长,远低于 Full GC
- 并发清除(并发,无 STW)
- 并发清理死亡对象,标记 - 清除
- 不移动对象,产生内存碎片
触发 Full GC 场景
- 老年代空间不足,并发回收跟不上对象产生速度
- 碎片过多,大对象无法分配
- 显式调用
System.gc()
G1 收集器(Garbage-First)
定位:全堆回收、分区 Region 模型、可预测停顿、JDK8 默认主力
核心:化整为零、优先回收垃圾最多的区域、混合回收
关键前置:Region 分区
- 把整个堆拆分为多个大小相等的 Region
- 逻辑划分:Eden、Survivor、Old、Humongous(大对象区)
- G1 不要求完整分代连续内存,灵活管理
G1 完整五大阶段(3 次 STW)
- 初始标记(STW)
- 同 CMS:只标记 GC Roots 直达对象
- 伴随一次普通 Minor GC,速度很快
- 并发标记(并发)
- 和用户线程并发执行
- 遍历全堆引用链,统计每个 Region 垃圾占比
- 计算「回收收益」
- 最终标记(STW)
- 修正并发阶段引用变动,处理漏标对象
- 筛选回收(STW,核心)
- 根据停顿时间目标,优先选择垃圾最多的 Region
- 批量回收、复制存活对象、压缩内存
- G1 是复制 + 标记整理,无内存碎片
- 混合回收
- 不是单独阶段,是 G1 常态
- 一次 GC:回收部分新生代 + 部分垃圾最多老年代 Region
- 减少 Full GC 频率
G1 额外核心特点
- 算法:
- 整体:分代收集
- 局部:复制 + 标记整理 → 无碎片
- 核心优势
- 可指定最大停顿时间
-XX:MaxGCPauseMillis - 大对象单独存 Humongous 区,避免老年代拷贝
- 兼顾吞吐量 + 低延迟
- 适用场景:堆内存较大、需要可控停顿的业务
CMS vs G1 高频对比
| 维度 | CMS | G1 |
|---|---|---|
| 回收范围 | 只负责老年代 | 全堆统一管理 |
| 算法 | 标记 - 清除(碎片) | 分区复制 + 整理(无碎片) |
| 停顿控制 | 不可预测 | 可预设停顿时间 |
| 大对象 | 劣势 | 单独 Humongous 区,友好 |
| 碎片问题 | 严重 | 几乎没有 |
| JDK 版本 | JDK8 常用,逐步淘汰 | JDK8 默认、JDK9 + 默认 |
ZGC
ZGC 核心定位
- JDK11 推出、JDK15 正式默认
- 超低延迟、TB 级大堆、几乎全程并发
- 核心目标:STW 控制在 10ms 以内
- 关键技术:染色指针(Colored Pointers)+ 读屏障
- 算法:并发标记 + 并发转移整理,无内存碎片
核心前置:染色指针
64 位指针高几位存储标记状态,不占用堆内存:
Final:最终存活Remapped:已重映射(整理后新地址)Marked0 / Marked1:交替标记周期优势:
- 无需在对象头存标记位
- 全局随时、快速判定对象状态
- 实现并发标记、并发整理、并发销毁
染色指针结构
64 位 CPU 理论寻址:2^64 地址
ZGC 只用到低 42 位存真实内存地址。
不占用对象头、不占用堆内存,直接把 GC 状态打在「对象地址指针」上。
1 | |
Marked0 本轮存活标记
Marked1 下一轮存活标记(交替使用)
Remapped 已转移 / 重映射(对象搬家完成)
Finalizable 仅可被 finalize 回收
染色指针 核心工作机制
1. 地址 = 地址 + GC 状态
普通垃圾收集器:
- 存活标记、转移状态 存在 对象头 / 位图 里
ZGC:
- 不改对象、不占堆内存
- 直接给地址 “上色”,指针自带 GC 状态
2. 并发标记阶段怎么用?
GC 遍历引用时:
- 存活对象 → 把指针打上
Marked0 / Marked1染色标记 - 垃圾对象 → 不打标记
业务线程并发运行,只要读取引用,读屏障自动校验染色位,判断对象死活。
3. 并发整理(对象搬家)怎么用?
ZGC 最强:并发移动对象,不 STW
- 把旧 Region 里存活对象,复制到新 Region
- 新地址打上 Remapped(已重映射)
- 旧引用还指向旧地址(带旧染色标记)
读屏障拦截:
业务线程访问旧地址时:
自动识别「旧地址未重映射」→ 转发到新真实地址
全程业务无感知、不停机。
ZGC 完整 6 大阶段
仅 2 次短暂 STW,其余全并发
1. 初始标记(STW,极短)
- 只扫描 GC Roots
- 毫秒级停顿,只抓第一层引用
- 唯一目的:开启本轮并发标记
2. 并发标记(全程并发)
- 用户线程正常运行,无卡顿
- 从 GC Roots 出发,遍历全堆引用链
- 依靠染色指针标记存活对象
- 全程和业务线程并发执行
3. 并发预备重分配(并发)
- 统计各区域存活占比
- 选出垃圾最多、回收收益最高的分区
- 标记需要并发转移整理的区域
4. 重定位标记(STW,极短)
- 短暂停顿
- 完成剩余根引用重映射修正
- 衔接下一阶段并发整理
5. 并发重分配 / 整理(核心)
- ZGC 最强特性:并发压缩、并发移动对象
- 将选中分区存活对象,复制到新空闲 Region
- 旧区域直接回收
- 通过读屏障:访问旧地址 → 自动转发到新地址
- 全程业务不停、无长停顿
6. 并发重置(并发)
- 清空本轮标记、重映射状态
- 染色指针状态复位
- 为下一次 GC 循环做准备
ZGC 三大关键技术
染色指针
地址自带 GC 状态,节约堆内存,快速标记、转发地址。
读屏障 Load Barrier
每次对象读取拦截,自动处理:标记更新、旧地址转发,保证并发移动安全。
分区域 + 动态回收
同 G1 Region 思想,只回收垃圾多的区域,不用整堆全量回收。
ZGC 优缺点
优点
- 极低停顿:STW 固定 1~10ms,和堆大小无关
- 支持 TB 级超大堆
- 全程并发压缩,无内存碎片
- 吞吐量损耗可控,远优于 CMS
- 不分代设计(JDK21+ 引入分代 ZGC,进一步提升吞吐)
缺点
- 大量读屏障,轻微损耗 CPU
- JDK11 后才稳定,老项目不兼容