引言
JVM发生频繁 CMS GC,罪魁祸首是这个参数!_-xx:+cmsscavengebeforeremark-CSDN博客
https://blog.csdn.net/zl1zl2zl3/article/details/89087327 基于以上文章频繁cms gc的生产问题得出的个人总结
总结
物理分代场景下,ygc发生promotion failure都会触发full gc,但是parNew+cms组合有些非常规。在以上真实案例中,频繁发生的那些 CMS GC(每2秒一次)都没有触发 concurrent mode failure,而只有最后一次(在并发标记阶段遇到 YGC)才触发说明 :promotion failed 是否导致 concurrent mode failure,取决于它发生在 CMS 周期的哪个子阶段。
📌 核心区分:两个不同的"失败"场景
| 场景 | 发生时间 | 是否导致 concurrent mode failure |
你的日志中是否出现 |
|---|---|---|---|
| 场景 A | CMS Final Remark 阶段 (由于 -XX:+CMSScavengeBeforeRemark 强制 YGC) |
否 | 频繁出现 (每次 CMS GC 都有 promotion failed,但 CMS 周期继续走完) |
| 场景 B | CMS 并发标记阶段 (正常的 Allocation Failure 触发 YGC) |
是 | 只出现一次(最后一次,引发 Full GC 兜底) |
🔬 为什么"频繁的 CMS GC"没有触发 concurrent mode failure?
你看到的现象是:每次 CMS 周期都正常执行了 Initial Mark → Concurrent Mark → Preclean → Final Remark → Sweep → Reset,尽管每次在 Final Remark 阶段都发生了 promotion failed,但 CMS 周期仍然完整结束(CMS-concurrent-sweep 和 CMS-concurrent-reset 都完成了)。
原因在于 :Final Remark 阶段发生的 promotion failed 并不会让 JVM 放弃本次 CMS 周期,因为:
-
Final Remark 是 STW 的最后一个阶段 :此时并发标记已经完成,CMS 已经知道哪些对象是垃圾。接下来的 Sweep 阶段只是清理垃圾,不需要额外的晋升空间。
-
promotion failed只表示"本次 YGC 没成功" :由于CMSScavengeBeforeRemark强制执行的 YGC 本身是"可选的"(目的是减少 remark 扫描时间),即使失败了,JVM 仍然可以继续执行 remark 和 sweep。晋升失败的对象会被留在年轻代(to-space 非空),但不会破坏 CMS 周期的完整性。 -
JVM 会"硬着头皮"完成本次 CMS 周期:因为最耗时的并发标记已经完成,放弃代价太高。JVM 选择继续,希望通过 sweep 释放一些老年代空间,缓解压力。
于是形成了死循环 :每次 CMS 周期都因为 promotion failed 没有实际回收多少老年代(甚至老年代使用量还略增),导致老年代占用率仍然 ≥80%,于是立即触发下一次 CMS 周期。如此往复,没有 concurrent mode failure,只有无尽的正常 CMS 周期。
【注:】
CMS-Final-Remark前置的YGC是简化版本,它仅能对正常年轻代进行回收。
例如第一次CMS GC,CMS-Final-Remark阶段前置的YGC执行发生了promotion failure(导致eden、survivor to非空),当第二次CMS GC,CMS-Final-Remark阶段前置的YGC发现survivor to非空就直接结束。
例如第一次CMS GC,CMS-Final-Remark阶段前置的YGC执行发生了promotion failure(导致eden、survivor to非空),也不会触发full gc。
💥 为什么最后一次触发了 concurrent mode failure?
根据你的描述,最后一次 CMS GC 在并发标记阶段 (CMS-concurrent-mark 尚未结束时)发生了一次由 Allocation Failure 触发的 YGC。这次 YGC 也发生了 promotion failed,但这次导致了 concurrent mode failure。
原因 :并发标记阶段是老年代压力最大的时期。CMS 正在标记存活对象,此时如果 YGC 需要晋升对象到老年代,而老年代没有足够的连续空间,JVM 会判定"当前 CMS 周期已经无法安全完成",因为后续的标记和清除需要依赖老年代的稳定性。于是立即抛出 concurrent mode failure,放弃当前 CMS 周期,退化为 Serial Old Full GC。
📖 回顾你的日志验证
-
频繁 CMS GC 日志 (例如 20:05:25.466):
[ParNew: 649871K->649871K(1887488K), 0.0000289 secs]在[GC (CMS Final Remark)内部发生,没有concurrent mode failure,CMS 继续走完 sweep(20:05:32.468[CMS-concurrent-sweep: 6.221/6.233 secs])。 -
最后一次崩溃日志 (20:14:17.847):
[GC (Allocation Failure)发生在CMS-concurrent-mark期间,紧接着(concurrent mode failure),然后触发长达 3.25 秒的 Full GC。