2. JVM 内存模型与 GC 基础:堆内存结构、新生代与老年代、GC 触发条件、STW 概念
在深入排查 CMS GC 异常之前,必须建立对 JVM 内存模型与垃圾回收机制的扎实理解。本章将聚焦于堆内存的核心分区、对象晋升路径、GC 触发条件以及 Stop-The-World(STW)这一关键概念,为后续分析 CMS 并发失败、晋升失败等异常奠定理论基础。
2.1 堆内存结构:分代设计的核心
JVM 堆是 GC 管理的主要区域,采用分代收集策略,基于“大部分对象朝生夕死”的统计规律,将堆划分为新生代(Young Generation)和老年代(Old Generation)。
核心原则:新生代存放生命周期短的对象,老年代存放长期存活的对象。不同代采用不同的回收算法,以平衡吞吐量与延迟。
2.2 新生代与老年代:对象生命周期管理
2.2.1 新生代 (Young Generation)
- Eden 区:大多数对象首先在 Eden 区分配。当 Eden 区空间不足时,触发 Minor GC。
- Survivor 区(S0 / S1):用于存放 Minor GC 后存活的对象。S0 和 S1 大小相等,同一时刻只有一个处于活跃状态(From),另一个为空(To)。
- 对象晋升:每经历一次 Minor GC 仍存活的对象,年龄加 1。当年龄达到阈值(默认 15,可通过
-XX:MaxTenuringThreshold设置)时,晋升到老年代。
2.2.2 老年代 (Old Generation)
- 存放长期存活的对象,以及从新生代晋升的大对象。
- 老年代空间不足时触发 Major GC 或 Full GC。
- 在 CMS 收集器中,老年代是并发收集的主要目标区域。
调优提示:如果频繁发生对象晋升失败(Promotion Failed),通常意味着老年代空间不足或 Survivor 区过小,导致对象提前进入老年代。可通过调整
-Xmn(新生代大小)或 -XX:SurvivorRatio 来优化。
2.3 GC 触发条件
不同 GC 事件的触发条件如下表所示:
| GC 类型 | 触发条件 | 影响范围 | 典型耗时 |
|---|---|---|---|
| Minor GC | Eden 区空间不足 | 新生代(Eden + Survivor) | 几毫秒 ~ 几十毫秒 |
| Major GC | 老年代空间不足 | 老年代 | 几百毫秒 ~ 几秒 |
| Full GC | 老年代空间不足、元空间不足、System.gc() 调用、CMS 并发模式失败 |
整个堆 + 元空间 | 几秒 ~ 几十秒 |
| CMS 并发模式失败 | CMS 回收期间老年代被填满 | 老年代(退化为 Serial Old 单线程回收) | 可能长达几十秒 |
注意:在 CMS 收集器中,并发模式失败(Concurrent Mode Failure)是最严重的异常之一。它意味着 CMS 在并发标记或并发清理阶段,老年代空间已被耗尽,导致 JVM 暂停所有应用线程,使用 Serial Old 收集器进行单线程 Full GC,造成长时间的 STW。
2.4 Stop-The-World (STW) 概念
STW 是垃圾回收过程中,JVM 暂停所有应用线程(除 GC 线程外)的行为。在 STW 期间,应用程序完全停止响应,用户请求被阻塞。
2.4.1 STW 的必然性
- 根节点枚举:GC 需要从 GC Roots(栈帧中的局部变量、静态变量等)出发,标记所有存活对象。此过程必须在一个一致性的快照中进行,否则对象引用关系会不断变化,导致标记结果不准确。
- 安全点(Safepoint):JVM 只在所有线程到达安全点时才能进入 STW。安全点通常位于循环末尾、方法返回、异常抛出等位置。
2.4.2 STW 对应用的影响
| 指标 | 说明 | 典型阈值 |
|---|---|---|
| 暂停时间 | 单次 STW 的持续时间 | CMS 目标 < 200ms;G1 目标 < 100ms |
| 暂停频率 | 单位时间内 STW 发生的次数 | 通常每分钟几次到几十次 |
| 吞吐量 | 应用线程运行时间占总时间的比例 | 通常要求 > 95% |
CMS 的设计哲学:CMS 通过将大部分 GC 工作与应用程序并发执行,来减少 STW 时间。但 CMS 仍然需要两次短暂的 STW:初始标记(Initial Mark)和重新标记(Remark)。初始标记仅标记 GC Roots 直接引用的对象,耗时极短;重新标记修正并发标记期间发生变化的对象引用,耗时稍长。
2.5 关键参数速查表
| 参数 | 默认值 | 说明 |
|---|---|---|
-Xms |
物理内存的 1/64 | 堆初始大小 |
-Xmx |
物理内存的 1/4 | 堆最大大小 |
-Xmn |
堆的 1/3 | 新生代大小 |
-XX:SurvivorRatio |
8 | Eden : Survivor 比例(如 8:1:1) |
-XX:MaxTenuringThreshold |
15 | 对象晋升老年代的年龄阈值 |
-XX:+UseConcMarkSweepGC |
关闭 | 启用 CMS 收集器 |
-XX:CMSInitiatingOccupancyFraction |
68 | 老年代使用率达到该百分比时触发 CMS 回收 |
实战建议:在数据库迁移场景中,由于数据量激增或连接池膨胀,老年代占用率可能快速上升。建议将
-XX:CMSInitiatingOccupancyFraction 适当调低(如 60%),提前触发 CMS 回收,避免并发模式失败。