线上频繁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参数 → 压测验证上线

相关推荐
wdfk_prog22 分钟前
嵌入式面试真题第 10 题:高优化等级下共享状态可见性、内存模型与系统级同步设计
java·linux·开发语言·面试·职场和发展·架构·c
卓怡学长31 分钟前
w255基于springboot仓库管理系统
java·数据库·spring boot·spring·intellij-idea
这个DBA有点耶3 小时前
SQL调优进阶:从“优化一条SQL”到“优化一个系统”的思维升级
java·大数据·数据库·sql·程序人生·dba·改行学it
csdn2015_4 小时前
springboot读取配置的方法
java·spring boot·spring
AOwhisky4 小时前
下一代容器来了?Docker 宣布原生支持 WebAssembly
java·运维·docker·容器·rust·wasm
云云只是个程序马喽6 小时前
海外短剧平台搭建方案:私有化源码系统选型|云微短剧系统技术架构拆解
java·php
C137的本贾尼7 小时前
第七篇:消息队列(MQ)——就是个带存储的异步通信管道
java·开发语言·中间件
Flittly7 小时前
【AgentScope Java新手村系列】(19)多模态-图像音频视频
java·spring boot·spring
Javatutouhouduan7 小时前
国内大厂Java面试高频题库(2026突击版)
java·架构师·java面试·java面试题·后端开发·java程序员·java八股文
Tim_107 小时前
【C++】017、new/delete与malloc/free的区别
java·数据结构·算法