【JVM进阶与实战系列】篇四:垃圾回收机制与经典收集器:CMS、G1 与 ZGC(GC篇)

垃圾回收(Garbage Collection, GC)是 JVM 自动化内存管理的核心,也是 Java 程序员区别于 C/C++ 程序员的幸福所在。然而,在面对高并发、大流量的生产环境时,GC 引发的全局停顿(STW, Stop-The-World)往往是导致系统卡顿、接口超时的罪魁祸首。理解 GC 的底层原理与经典收集器的演进,是进行极限性能调优的必备基本功。本文将带你深入 JVM 的垃圾回收机制,从基础算法一路打通至 ZGC 的黑科技。

一、 垃圾的判别:生死宣判的底层规则

要回收垃圾,首先要准确地找出内存中哪些对象已经“死亡”。JVM 摒弃了存在循环引用致命缺陷的“引用计数法”,全面采用了可达性分析算法 (Reachability Analysis)

1. 可达性分析的核心逻辑

该算法引入了一系列被称为 GC Roots(根节点) 的对象作为绝对安全的起始点,向下顺藤摸瓜搜索引用的对象。搜索走过的路径称为引用链。

  • 可达对象:有链路连向 GC Roots,判定为存活。

  • 不可达对象:孤立在引用链之外,即使对象之间有互相引用(循环引用),只要连不到 Roots,统统判定为垃圾。

2. 🔥 面试必背:谁有资格做 GC Roots?

为了保证内存的安全清算,JVM 将以下四类处于“绝对活跃状态”的对象设为根:

  1. 虚拟机栈(栈帧中的局部变量表)中引用的对象:正在运行的方法中的局部变量、参数。

  2. 方法区中类的静态属性、常量引用的对象:它们随类的加载而存在,生命周期长,是稳定的引用源。

  3. 本地方法栈中 JNI(Native 方法)引用的对象:跨语言调用时的安全保障。

  4. 所有被同步锁(synchronized)持有的对象:正在被多线程竞争或使用的资源,绝对不可回收。

二、 三大基础清理算法:时间与空间的博弈

找到垃圾后,JVM 演进出了三种核心清理算法,它们是现代复杂垃圾回收器的基石:

算法名称核心操作逻辑致命痛点核心适用场景
标记-清除 (Mark-Sweep)先标记出所有垃圾,然后直接打扫原地清除。会产生大量不连续的内存碎片。大对象分配时易提前触发 GC。CMS 的老年代回收。
标记-整理 (Mark-Compact)标记后,将所有存活对象向内存一端移动排齐,直接切掉边界外垃圾。涉及大量对象的物理移动并需更新引用地址,效率极低,STW 时间长老年代的兜底回收 (如 Serial Old)。
复制算法 (Copying)将内存划分为两块,每次只用一块;满后将存活对象搬家到另一块,一次性清空原内存。极度浪费空间,内存可用率直接减半(未优化前)。存活率极低的年轻代(优化为 8:1:1 结构)。

三、 并发 GC 的底层基石:三色标记法与漏标危机

传统的标记过程需要全程 STW(暂停所有用户线程),导致系统停顿时间过长。为了让垃圾回收线程与用户线程并发执行,CMS 和 G1 底层都采用了三色标记法

1. 颜色的定义

三色标记法将内存中的对象按“扫描进度”分为三种颜色:

  • 白色(未被标记):尚未被垃圾回收器访问过。若标记结束仍为白色,则判定为垃圾。

  • 🔘 灰色(标记进行中):该对象已被访问过,但它引用的子对象还没有被完全扫描完

  • 黑色(标记已完成):该对象及其所有引用的子对象都已被扫描完毕,绝对安全。

2. 致命危机:漏标(对象消失)问题 🔥

当用户线程在 GC 并发标记期间修改了引用关系,活对象可能会被误当成垃圾清理掉,导致系统崩溃。发生漏标必须同时满足以下两个条件

  1. 黑连白:黑色对象新增了一条或多条指向白色对象的引用。

  2. 灰断白:灰色对象删除了指向该白色对象的所有直接或间接引用。

3. 工业级破局方案:CMS vs G1

只要破坏上述任意一个条件,就能解决漏标:

  • CMS 的解法:增量更新 (Incremental Update)

    • 思路:破坏条件 1(针对新增)。

    • 机制:一旦黑色对象指向了白色对象,就把这个黑色对象记录下来;在重新标记阶段,把它降级回灰色重新扫描一遍。

  • G1 的解法:原始快照 (SATB, Snapshot At The Beginning)

    • 思路:破坏条件 2(针对删除)。

    • 机制:一旦灰色对象断开了对白色对象的引用,就把这个白色对象记录下来;在最终标记阶段,强行将其标记为黑色(存活),“宁可错放产生浮动垃圾,也绝不漏标错杀”。

四、 工业级双雄对比:CMS 与 G1 的架构博弈

1. CMS (Concurrent Mark Sweep) —— 响应时间优先的老派王者

CMS 针对老年代设计,极力减少 STW 时间,适用于对延迟敏感的 C 端系统。

  • 核心四步走

    1. 初始标记 (STW):仅标记 GC Roots 直接关联对象,极快。

    2. 并发标记 (并发):顺藤摸瓜遍历对象图,耗时最长但不卡顿。

    3. 重新标记 (STW):处理并发阶段产生的漏标变动。

    4. 并发清理 (并发):采用标记-清除算法清理垃圾。

  • 两大致命痛点

    • 内存碎片陷阱:因采用标记-清除,老年代会布满碎片。遇到大对象无法分配时,会退化为 Serial Old 单线程收集器,引发长达秒级的恐怖 STW。

    • 浮动垃圾:并发清理时用户线程产生的“新垃圾”无法当次处理,若浮动垃圾撑爆老年代(Concurrent Mode Failure),同样会导致系统崩溃。

2. G1 (Garbage-First) —— 现代化可预测停顿军团

G1 面向大内存服务器,追求吞吐量与低延迟的完美平衡,并在 JDK 9 之后成为默认收集器。

  • 颠覆性架构:Region 分区:打破了年轻代与老年代的物理隔离,将整个堆划分为几百上千个大小相等的 Region(区域)。每个 Region 动态扮演 Eden, Survivor, Old 或存放超大对象的 Humongous 角色。

  • 核心优势:可预测停顿与无碎片

    • G1 每次不会扫描全堆,而是根据用户设定的期望停顿时间(如 200ms),优先挑选垃圾最多、收益最大的 Region 进行回收(Garbage-First 的由来)。

    • 采用复制算法进行 Region 间的对象搬运清理,完美解决了 CMS 的内存碎片痛点。

五、 GC 的未来:ZGC 超低延迟黑科技深度剖析

ZGC 是 JDK 11+ 引入的划时代收集器,打破了“堆内存越大,GC 停顿越久”的物理魔咒。

  • 恐怖指标:无论堆内存是 8MB 还是 16TB,ZGC 都能将 STW 停顿时间永远控制在 1ms 以下(JDK 16+)。

  • 核心黑科技(面试绝杀)

    1. 染色指针 (Colored Pointers):ZGC 不去对象头里存 GC 状态,而是直接把状态信息(如对象是否被移动过)写在 64 位指针的高位地址里!读取指针的瞬间就能知晓对象状态,速度极快。

    2. 读屏障 (Load Barrier) 与指针自愈 (Self-Healing):当存活对象被 GC 搬家后,如果有用户线程拿着旧地址去访问,底层的“读屏障”会瞬间拦截。它通过转发表查到新地址返回给用户,并顺手把旧指针改写成新指针(指针自愈)。第二次访问时直接就是新地址,毫无卡顿,彻底免去了 G1 那种需要 STW 来统一更新引用的代价。

六、 线上 GC 触发条件与选型指南

1. 垃圾回收器选型四大维度

维度考量推荐选型
单核/微小内存Serial 家族(避免多线程上下文切换开销)。
吞吐量优先 (如后台跑批)Parallel Scavenge + Parallel Old (允许较长 STW 换取极致计算性能)。
低延迟 & 小内存 (< 4GB/8GB)CMS (因 G1 维护 Region 元数据耗费较多内存,小内存下负担重)。
低延迟 & 大内存 (8GB+)G1 (利用分区回收优势,避免大内存的全堆扫描)。
极致毫秒级延迟ZGC (需 JDK 11+ 环境)。

2. Full GC 核心触发场景汇总

坚决避免 Full GC 是调优的终极 KPI。以下情况会强制触发全堆清理:

  1. 老年代空间不足:经过多次 Minor GC 晋升或大对象直达,把老年代塞满。

  2. 碎片严重:CMS 产生大量碎片,导致大对象无法找到连续空间分配。

  3. 空间分配担保失败:Minor GC 前发现老年代最大可用连续空间小于历次晋升平均大小。

  4. 元空间 (Metaspace) 爆满:动态加载的类过多达到阈值。

  5. 显式调用 System.gc():代码中手动建议触发(极不推荐)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值