java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java线上频繁FullGC

Java线上频繁FullGC的完整排查流程

作者:码不停蹄的玄黓

这段文章详细介绍了FullGC的的常见触发原因原因、排查思路和优化方法,涵盖老年代不足、内存泄漏、MetaSpace元空间满等方面,并提供了从紧急应急到长期优化的全面解决方案,需要的朋友可以参考下

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

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

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

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

1. 基础信息采集

# 查看java进程PID
jps -l
# 实时看GC状态
jstat -gcutil PID 1000

重点指标:

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

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

curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar

常用排查命令

# 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:大对象频繁创建直接进老年代

场景3:元空间满FGC

场景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老项目优化

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

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

九、排查速记口诀

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

到此这篇关于Java线上频繁FullGC的完整排查流程的文章就介绍到这了,更多相关Java线上频繁FullGC内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文