线上频繁FullGC完整排查流程

一、先明确:FullGC触发常见原因

  1. 老年代空间不足、内存泄漏/内存溢出
  2. 大对象直接进老年代、频繁晋升
  3. MetaSpace元空间满、动态加载类过多
  4. System.gc()代码主动调用、RMI定时FullGC
  5. JVM参数不合理:新生代太小、Survivor过小、晋升阈值异常
  6. 堆内存分配过小、物理内存被其他进程抢占

核心排查思路:先抓现场 → 看GC日志定位类型 → 堆快照分析 → 代码定位 → 压测复现 → 调参优化

二、阶段1:紧急应急(线上不能停服优先)

1. 基础信息采集

shell 复制代码
# 查看java进程PID
jps -l
# 实时看GC状态
jstat -gcutil PID 1000

重点指标:

  • O(老年代使用率)持续上涨 → 内存泄漏/对象不停晋升到老年代
  • M(元空间)持续涨满 → 类加载泄漏
  • S0/S1长期几乎满、Eden快速打满 → 新生代过小,对象频繁提前晋升老年代

2. 临时规避(业务优先保可用)

  1. 临时扩容-Xmx/-Xms,增大堆,缓解频繁FGC
  2. 临时关闭RMI自动FullGC:-Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000
  3. 临时下线可疑定时任务、批量导入、大文件解析接口

三、阶段2:采集GC日志,定位FGC根因分类

1. 开启/导出GC日志(没提前配置则动态抓取)

JVM启动参数必备(生产标配):

复制代码
-Xloggc:/xxx/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC
根据GC日志区分4大类FGC:
  1. 老年代占满触发FGC(最常见)
    日志关键字:Allocation Failure,Eden耗尽→对象晋升老年代,老年代不足触发FullGC
  2. MetaSpace满触发FGC
    日志:Metaspace allocation failure,动态代理、热加载、频繁创建ClassLoader
  3. System.gc()主动触发
    日志:Full GC (System.gc()),代码/第三方包显式调用System.gc()
  4. CMS并发失败/晋升担保失败(CMS收集器)
    concurrent mode failure / promotion failed:老年代预留空间不足,担保失败触发STW FullGC

四、阶段3:dump堆快照,定位占用内存的对象(核心步骤)

抓堆时机:FGC频繁、老年代占用率80%+还没OOM时dump,避免OOM后数据丢失

方式1:命令行dump

shell 复制代码
jmap -dump:format=b,file=heap.hprof PID
# 生产dump建议加参数,不阻塞业务:
jmap -dump:live,format=b,file=heap.hprof PID

方式2:OOM自动dump(提前配置)

复制代码
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/xxx/heap.hprof

堆文件分析工具

  1. MAT(Eclipse Memory Analyzer,首选)
    • Leak Suspects:泄漏可疑报告(一键定位泄漏对象)
    • Dominator Tree:支配树,查看占用堆最多的对象
    • Histogram:按类统计实例数量、占用内存
  2. JProfiler、Arthas在线分析(不用下载大hprof)

五、阶段4:Arthas在线实时排查(不用重启、不用dump大文件)

1. 安装启动Arthas

shell 复制代码
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar

常用排查命令

bash 复制代码
# 1. 实时监控GC
gc -i 1000
# 2. 查看堆内存实时分布
heapdump --live /tmp/dump.hprof
# 3. 查看对象实例数量,快速找暴涨对象
dashboard
# 4. 追踪创建大量对象的方法
trace 全类名 方法名
# 5. 排查ClassLoader泄漏
classloader

六、阶段5:按场景逐个定位代码问题

场景1:内存泄漏(老年代缓慢涨,每次FGC回收不掉多少内存)

常见泄漏点:

  1. 静态集合(static List/Map)无限add对象,无移除逻辑
  2. ThreadLocal使用完未remove,线程池复用线程导致对象常驻
  3. 连接池、缓存(本地Cache)无过期淘汰,数据只增不减
  4. 第三方框架:Mybatis拦截器、定时任务、异步线程未销毁对象

MAT定位:支配树找到超大对象 → 查看引用链Reference Chain → 找到业务代码

场景2:大对象频繁创建直接进老年代

  • 一次性读取超大文件、全表查数据库不分页返回全量List
  • 超大字符串拼接、一次性创建超大数组
    JVM参数:-XX:PretenureSizeThreshold=2M 超过阈值直接进老年代,频繁创建触发FGC

场景3:元空间满FGC

  • 动态生成代理类(CGLIB频繁创建)、Groovy脚本热加载
  • 自定义ClassLoader不停新建不卸载,-XX:MaxMetaspaceSize配置过小

场景4:代码主动System.gc()

  1. 第三方SDK、老版本RPC、定时任务里隐式调用System.gc()
  2. 代码检索System.gc()全局关键字,使用Arthas watch拦截调用

七、阶段6:JVM参数优化(分收集器:G1/CMS/ZGC)

通用优化方向

  1. 新生代大小:Eden占堆1/3~1/2,避免Eden过小频繁晋升
  2. 调整SurvivorRatio、MaxTenuringThreshold晋升年龄,减少过早晋升到老年代
  3. 禁用显式GC:-XX:+DisableExplicitGC(谨慎:NIO依赖System.gc堆外内存则不能加)
  4. 合理设置MetaSpace:-XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=512M

G1收集器(主流生产)

复制代码
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200 # 预期STW停顿
-XX:G1HeapRegionSize=16M

CMS老项目优化

  • 调高老年代预留空间:-XX:CMSInitiatingOccupancyFraction=70 -XX:+UseCMSInitiatingOccupancyOnly,避免并发失败FullGC

八、阶段7:复现验证+上线落地优化

  1. 本地/测试环境压测(JMeter)复现FGC,修复代码后压测验证GC平稳
  2. 上线灰度发布,上线后持续监控GC指标(Prometheus+Grafana)
  3. 告警配置:老年代使用率>80%、10min内FullGC>3次触发告警

九、排查速记口诀

先jstat看GC趋势 → 拉GC日志分FGC类型 → arthas在线查对象 → dump堆MAT找泄漏引用 → 改代码+调JVM参数 → 压测验证上线

相关推荐
再玩一会儿看代码1 小时前
Java浅拷贝和深拷贝理解笔记
java·linux·开发语言·笔记·python·学习
兔老大RabbitMQ1 小时前
IDEA 打字打在光标右边 / 删除异常问题
java·ide·intellij-idea
jeffer_liu1 小时前
Spring AI 生产级实战:多模态
java·人工智能·后端·spring·大模型
码不停蹄的玄黓1 小时前
Arthas 最常用命令速查表
java
石榴树下的七彩鱼1 小时前
发票OCR识别API接入教程:从图像到结构化数据的完整实战(附Python/Java/PHP/JS代码)
java·python·ocr·api接口·财务自动化·石榴智能·发票ocr
爱吃羊的老虎1 小时前
【JAVA】Java微服务—分布式事务框架Seata
java·开发语言
Jul1en_2 小时前
【Redis】事务详解、WATCH 实现思想
java·spring boot·redis·mysql·java-ee
SimonKing2 小时前
你还在靠重启来调线程池?别人已经做到了实时调控,3分钟接入
java·后端·程序员
小张小张爱学习2 小时前
Java并发编程面试题
java·开发语言