G1垃圾回收器启动时CPU飙升,通常不是单一原因,而是内存、代码和配置三者共同作用的结果。我们可以从两个层面来分析:正常GC工作导致的升高和异常问题导致的飙升。
- 正常的GC工作负载
G1的设计目标是可预测的停顿,但它遵循标记-整理算法,某些阶段本身就是CPU密集型操作。
· 初始标记阶段:需要停止应用线程(STW)。虽然时间很短,但如果在高并发时触发,瞬间的上下文切换会让CPU占用率出现一个"尖峰"。
· 并发标记阶段:这是CPU消耗的主要来源。GC线程与应用线程并发执行,会扫描整个堆中存活对象,这涉及大量的内存读取和图遍历操作,会持续占用CPU资源。
· 混合收集阶段:G1会计算各区域的回收价值。当计算压力大,或选定大量CSet(回收集合)进行并行回收时,多线程压缩对象也会导致CPU短暂冲高。
- 异常飙升的常见原因
如果CPU长时间居高不下,通常是以下问题导致:
· 频繁的GC(吞吐量下降):当对象分配速度极快,超过G1的回收速度时,会导致GC频繁执行。此时,CPU大量时间花在GC上,而非业务逻辑,表现为CPU飙升。
· 配置不当:
· 并行GC线程过多:G1默认的GC线程数可能与CPU核数相同。在容器环境(如只有2核)或与其他进程共用资源的机器上,过多线程竞争会导致CPU过高。
· 误区:滥用大内存:堆内存过大,并发标记周期变长,GC持续消耗CPU。
· "满屏脏卡"与写屏障开销:G1使用卡表(Card Table)来记录跨代引用。当业务代码产生大量对象引用变更(如高频更新Map或循环引用)时,写屏障(Write Barrier)的后台维护工作会加重CPU负担。
· 元空间(Metaspace)膨胀:如果动态加载大量类(如反射、CGLIB),元空间达到阈值会触发Full GC(使用串行回收)。这是一个单线程、STW的回收过程,会导致CPU瞬间飙高。
· 外部因素干扰:系统资源紧张导致GC线程被频繁挂起和调度,或服务器开启了未知的定时任务(如病毒扫描、日志压缩)与GC时间重叠。
排查思路
建议按以下步骤排查:
- 确认GC频率:使用 jstat -gcutil 1000 查看GC频率。如果YGC或FGC很频繁,基本可确定是内存压力大。
- 获取线程堆栈:CPU飙升时,用 top -H -p 找到CPU高的线程,再通过 jstack 导出日志,查看这些线程是 GC task thread 还是业务线程。
- 分析堆内存:通过 jmap 或 MAT(Eclipse Memory Analyzer Tool,内存分析工具) 分析,检查是否存在大对象、内存泄漏或元空间加载异常。
- 审查代码变更:重点关注大量创建对象、高频调用或复杂反射操作的上线代码。