百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 文章教程 > 正文

字节、阿里面试都在问的CMS GC问题:9个你一定会遇到的GC问题

yund56 2025-06-17 02:43 5 浏览

"生产环境Full GC频繁,RT超过3秒,该如何优化?" "面试官:讲讲你对CMS GC的理解?" "为什么堆内存还有40%,却频繁发生GC?"

如果你也曾被这些问题困扰,那这篇文章正是为你准备的。




开篇:一个真实的生产事故

凌晨3点,运维紧急电话打来:

"线上订单系统响应超时,监控面板一片飘红..."

登录系统一看,触目惊心:

  • GC频率: 2分钟一次Full GC
  • RT: P99从200ms飙升到3000ms
  • CPU: GC线程占用80%
  • 内存: Old区使用率不断攀升

这是一个典型的GC问题,但背后的原因可能有很多:

  • 内存泄漏?
  • 代码缺陷?
  • GC参数配置不当?
  • 还是别的什么原因?




让我们通过9个真实案例,深入探究CMS GC的各种问题场景,建立系统的GC问题诊断和优化方法。

为什么要写这篇文章?

在我10年+的Java开发生涯中,遇到最多的就是GC问题。每次面试必问的也是GC。但网上的GC文章要么太浅,要么太深,很少有完整的案例分析。

这篇文章将从实战出发:

  • 9个真实案例剖析
  • 每个案例都包含问题表现、底层原因、最佳实践
  • 配合源码级讲解
  • 完整的优化方案

无论你是初学者还是老手,都能从这篇文章中获得成长。

阅读指南

本文较长(预计阅读时间30分钟),建议:

  1. 先看目录,选择感兴趣的场景
  2. 准备好JVM工具:jstat、jmap、MAT等
  3. 动手实践,准备测试程序
  4. 收藏本文,便于日后查阅

让我们开始这段探索之旅吧!



九种CMS GC问题分析与解决方案详解

一、动态扩容引起的空间震荡

问题表现

  • 服务刚启动时GC次数较多
  • 最大空间有剩余但仍发生GC
  • GC Cause显示"Allocation Failure"
  • 每次GC后堆内存空间会被调整

底层原因



// JVM动态扩容的核心代码
void ConcurrentMarkSweepGeneration::compute_new_size() {
    // 如果增量收集失败,直接扩容到最大值
    if (incremental_collection_failed()) {
        grow_to_reserved();
        return;
    }
    
    // 根据当前使用情况计算新的大小
    CardGeneration::compute_new_size();
}

问题在于:

  • -Xms和-Xmx设置不一致
  • 初始化时只分配-Xms大小空间
  • 每次空间不足时要向OS申请内存
  • 申请内存过程中必然触发GC

解决方案

  1. 设置-Xms和-Xmx相等,避免动态扩容
  2. 合理设置-XX:MinHeapFreeRatio和-XX:MaxHeapFreeRatio
  3. 预估容量,给JVM足够的初始空间



二、显式GC的去与留

问题表现

  • System.gc()导致的Full GC
  • 频繁GC但每次回收量不大
  • GC日志中出现"System.gc()"

底层原因

public static void gc() {
    boolean shouldRunGC;
    synchronized(LOCK) {
        shouldRunGC = justRanFinalization;
        if (shouldRunGC) {
            justRanFinalization = false;
        } else {
            runFinalization();
        }
    }
    if (shouldRunGC) {
        runGC();
    }
}

显式GC的问题:

  • 会触发Full GC,STW时间长
  • 打断CMS的并发收集
  • 影响系统的吞吐量

解决方案

  1. 通过-XX:+DisableExplicitGC禁用System.gc()
  2. 使用-XX:+ExplicitGCInvokesConcurrent将System.gc()转为CMS GC
  3. 代码优化,避免调用System.gc()




三、MetaSpace区OOM

问题表现

  • MetaSpace空间耗尽
  • 出现OOM异常
  • 伴随着频繁的Full GC

底层原因



// MetaSpace的类加载机制
class ClassLoader {
    protected Class<?> loadClass(String name) 
        throws ClassNotFoundException {
        // 1. 查找已加载的类
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            // 2. 尝试加载新类
            c = findClass(name);
            // 3. 存储到MetaSpace
            defineClass(name, b, 0, b.length);
        }
        return c;
    }
}

MetaSpace问题常见原因:

  • 动态生成类导致类元数据信息过多
  • ClassLoader泄漏
  • 反射、动态代理使用不当

解决方案

  1. 设置合理的MetaSpace大小
-XX:MetaspaceSize=128m
-XX:MaxMetaspaceSize=256m
  1. 排查类加载和卸载情况
  2. 检查是否存在ClassLoader泄漏
  3. 合理使用动态代理等反射机制



四、过早晋升

问题表现

  • Young GC频繁
  • 对象过早进入老年代
  • 老年代空间增长过快

底层原因



// 动态年龄计算
if (age < MaxTenuringThreshold) {
    // 计算当前age及以下的对象总大小
    size_t total = 0;
    for (int i = age; i >= 0; --i) {
        total += sizes[i];
        if (total > survivor_capacity/2) {
            result = i;
            break;
        }
    }
}

过早晋升原因:

  • Survivor空间过小
  • MaxTenuringThreshold设置过小
  • 动态年龄判定导致过早晋升

解决方案

  1. 调整新生代大小
-XX:NewRatio=4
-XX:SurvivorRatio=8
  1. 调整对象晋升年龄
-XX:MaxTenuringThreshold=15 
  1. 关注动态年龄计算
  2. 检查是否存在大对象直接进入老年代



五、CMS Old GC频繁

问题表现

  • Old区频繁执行CMS GC
  • GC日志中出现大量"CMS Initial Mark"和"CMS Final Remark"
  • 每次回收效果不佳

底层原因



// CMS GC触发条件
void CMSCollector::shouldConcurrentCollect() {
    // 判断是否需要触发CMS GC
    if (_cmsGen->should_concurrent_collect()) {
        // 1. 根据空间占用率判断
        if (_cmsGen->occupancy() >= _bootstrap_occupancy) {
            return true;
        }
        
        // 2. 根据增长率判断
        if (isGrowingTooFast()) {
            return true;  
        }
    }
}

主要原因:

  • CMS启动阈值过高
  • 空间碎片化严重
  • 对象分配速率过快
  • 浮动垃圾过多

解决方案

  1. 调整CMS触发阈值
// 降低触发阈值,提前启动GC
-XX:CMSInitiatingOccupancyFraction=68
-XX:+UseCMSInitiatingOccupancyOnly
  1. 增大Old区空间
-XX:CMSMaxAbortablePrecleanTime=5000  
  1. 开启空间碎片整理
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction=0



六、单次CMS Old GC耗时长

问题表现

  • CMS GC单次停顿时间长
  • 出现较长的STW时间
  • Final Remark阶段耗时过长

底层原因



// CMS Final Remark阶段的核心代码
void CMSCollector::checkpointRootsFinalWork() {
    // 1. 处理Reference
    ReferenceProcessor::processReferences();
    
    // 2. 类卸载
    if (should_unload_classes()) {
        ClassUnloadingWork();
    }
    
    // 3. 清理符号表
    SymbolTable::clean_up();
}

耗时原因:

  • Reference处理慢
  • Class卸载耗时长
  • Symbol Table清理慢
  • 新生代状态不稳定

解决方案

  1. 优化Reference处理
// 开启并行Reference处理
-XX:+ParallelRefProcEnabled
  1. 控制Class卸载
// 关闭类卸载
-XX:-CMSClassUnloadingEnabled
  1. 稳定新生代状态
// Final Remark前强制Young GC
-XX:+CMSScavengeBeforeRemark



七、内存碎片&收集器退化



问题表现

  • CMS退化为Serial Old收集器
  • 出现"Promotion Failed"或"Concurrent Mode Failure"
  • Full GC频繁发生

底层原因



// CMS并发失败处理
void ConcurrentMarkSweepGeneration::collect_in_background() {
    if (shouldConcurrentCollect()) {
        // 并发收集失败,退化为Serial收集
        if (_foregroundGCIsActive) {
            collectSerially();
            return;
        }
        // 执行并发收集
        collectConcurrently();
    }
}

退化原因:

  • 空间碎片化严重
  • 并发模式失败
  • 晋升失败
  • 显式GC触发

解决方案

  1. 控制碎片率
// 配置碎片整理
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction=10
  1. 调整并发参数
// 增加并发线程
-XX:ConcGCThreads=4
// 提前启动CMS
-XX:CMSInitiatingOccupancyFraction=68
  1. 预留足够空间
// 增大老年代空间
-Xmx4g -Xms4g




八、堆外内存OOM



问题表现

  • 进程内存超出堆内存限制
  • 出现Direct Memory OOM
  • 堆外内存增长快速

底层原因

// DirectByteBuffer分配堆外内存
class DirectByteBuffer {
    DirectByteBuffer(int cap) {
        // 通过Unsafe分配本地内存
        base = unsafe.allocateMemory(size);
        // 通过Cleaner管理内存回收
        cleaner = Cleaner.create(this, new Deallocator(base, size));
    }
}

OOM原因:

  • DirectByteBuffer使用不当
  • 堆外内存泄漏
  • JNI调用未释放
  • 本地内存过大



解决方案

  1. 限制DirectMemory大小
-XX:MaxDirectMemorySize=256M
  1. 检查DirectByteBuffer使用
  2. 排查JNI调用
  3. 开启NMT监控
-XX:NativeMemoryTracking=detail



九、JNI引发的GC问题

问题表现

  • GC Locker导致的GC
  • Young GC停顿时间变长
  • 对象过早晋升

底层原因



// GC Locker机制
class GCLocker {
    static void lock() {
        // 阻止GC发生
        _needs_gc = true;
        _jni_lock_count++;
    }
    
    static void unlock() {
        _jni_lock_count--;
        if (_jni_lock_count == 0 && _needs_gc) {
            // 触发一次GC
            Universe::heap()->collect();
        }
    }
}

问题原因:

  • JNI临界区阻塞GC
  • 触发额外的GC
  • 导致对象提前晋升

解决方案

  1. 优化JNI代码
  • 减少JNI调用时间
  • 避免在JNI中长时间持有锁
  1. 添加GC参数
// 打印JNI引起的GC信息
-XX:+PrintJNIGCStalls
  1. 调整堆内存配置
// 增大新生代空间
-XX:NewSize=256m
-XX:MaxNewSize=256m



写在最后

以上九种CMS GC问题是最常见的场景,关键是:

  1. 建立完善的监控体系
  2. 保留现场和日志信息
  3. 掌握问题分析方法
  4. 积累优化调优经验

记住一句话:

"GC优化不是目的,解决业务问题才是根本。"

#Java性能优化 #GC调优 #JVM

相关推荐

Excel表格带单位求和不用愁!2个高效小技巧,轻松搞定!

我是【桃大喵学习记】,欢迎大家关注哟~,每天为你分享职场办公软件使用技巧干货!——首发于微信号:桃大喵学习记最近有小伙伴私信提问了个问题:“Excel表格数据带单位,如何快速求和?”。相信很多新手小伙...

[office] Excel中Sumproduct函数的使用方法-

Excel中Sumproduct函数的使用方法-SUMPRODUCT和SUMIFS是Excel的两个最强大的函数,用于从表中返回过滤的数据。SUMPRODUCT功能更强大,但SUMIFS更快。您可以...

SUMPRODUCT函数:关于多条件求和,不仅仅是SUMIFS,我也行!

文章最后有彩蛋!好礼相送!SUMPRODUCT函数,作为excel函数公式中的常用功能之一,运用及其广泛。结合它能够处理数据的功能,函数哥将它称之为多条件求和的函数,你可能有疑问了。SUMIF和SUM...

Excel函数公式大全之利用MMULT函数计算两个数组矩阵的乘积

各位Excel天天学的小伙伴们大家好,欢迎收看Excel天天学出品的excel2019函数公式大全课程。今天我们依旧要学习的是Excel函数中的数学函数MMULT函数,计算两个数组的矩阵乘积。今天这个...

Excel中的这个“万能函数”你用过吗?一个顶四个,简单又实用

Hello,大家好,今天跟大家分享一个Excel中的最强大的求和函数,它就是——SUMPRODUCT函数,很多人都将其称之为“万能函数”,条件求和,条件计数等一些常用的功能他就能轻松搞定,逻辑也非常的...

根据关键字条件求和,SUMPRODUCT函数思路清晰!

1职场实例小伙伴们大家好,今天我们来讲解一个关于根据关键字进行条件求和的职场真实案例,这是公众号粉丝后台留言咨询的一个问题,下面我们来通过几组简单的数据还原一下真实的办公情景。如下图所示:A列为一列地...

Excel-万能PRODUCT函数

sumproduct除了可以计算乘积之和,还可以实现单条件求和(代替sumif),多条件求和(代替sumifs),单条件计数(代替countif),多条件计数(代替countifs)我总结了一个通用的...

Excel“王者”级求和函数SUMPRODUCT,职场必学!

我是【桃大喵学习记】,欢迎大家关注哟~,每天为你分享职场办公软件使用技巧干货!日常工作中我们经常需要对Excel数据求和、计数,今天就跟大家分享一下Excel“王者”级求和函数SUMPRODUCT,灵...

SUMPRODUCT函数满足“或”的要求,实现多条件求和!

1职场实例小伙伴们大家好,今天我们来继续讲解Excel使用中非常实用且强大的函数:SUMPRODUCT函数,上一次我们讲到了SUMPRODUCT函数实现类似SUMIFS函数多条件求和的功能。而今天我们...

整列数据相乘再相加sumproduct函数#excel技巧

今天分享一下像这种表格,我想求它的消费,也就是用它的数量去乘以单价去加上。下一个的数量乘以单价要加上,下一个数量乘以单价。如果小白会这样一步一步的去算,去单价去乘以数量,然后加上单价去乘以数量,一个一...

双向多条件求和,sumifs彻底不行了,但是sumproduct却能轻松搞定

今天我们来解决一个困扰很多Excel新手的问题,它就双向求和,所谓的双向,就是两个方向,如下图所示,我们想要根据【项目】与【费用类别】来实现动态求和效果。【项目】与【费用类别】在数据源中,一个是纵横的...

Excel函数之Sumproduct,7个经典用法,你真的都了解吗?

什么是sumproduct函数以及其基本操作原理?sumproduct函数主要用于对数组中的数值进行相乘后再求和,。该函数最多支持255个参数(数组),这些数组可以是数字单元格引用或区域。它的工作流程...

WPS-Excel表格sumproduct函数一次性算出相乘相加总额

excel表格单纯相加或者相乘大家都会应用,但是有时候我们需要算出相乘相加的总额,这种计算也是可以一次性算出的。今天来教大家怎样在WPS表格中,让数据一次性算出相乘相加的总额,会了这个小技巧,会方便很...

大神级Sumproduct公式这么好用,1分钟学会!

在工作中,一般用不到Sumprodct函数公式,但是真的好用,我们举工作中的3个场景来说明。1、快速相乘相加如下所示,我们各种商品有一个单价,然后对应有一些数量,我们现在需要快速汇总总金额数据有没有小...

万能函数Sumproduct,除了求和和计数外,还可以排名

在众多的Excel函数中,能同时完成求和、计数以及排名功能的函数不多,其中Sumproduct就是其中一个。一、万能函数Sumproduct:功能及语法结构。功能:返回相应区域数组乘积的和。语法结...