基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

如何对频繁Full GC的应用进行排查

知识点图片

本文系统性讲解了排查Java应用频繁Full GC的方法。重点介绍了如何使用Arthas进行在线诊断,通过heapdump、stack、profiler等命令定位内存泄漏、大对象等根本原因。

对 Java 应用频繁 Full GC 进行排查是一个非常经典且重要的性能优化场景。下面我将为你提供一个系统化、由浅入深的排查思路,并重点介绍如何利用 Arthas 这一神器进行在线诊断。


排查方法论:四步走

处理任何性能问题,都可以遵循一个通用的模式:

  1. 确认问题 (Confirm): 确认 Full GC 真的频繁发生,并了解其频率和耗时。
  2. 定位原因 (Identify): 分析是什么原因导致了频繁的 Full GC。
  3. 解决问题 (Resolve): 针对原因进行代码或配置修复。
  4. 验证和预防 (Verify & Prevent): 验证修复效果,并建立长效机制防止问题复现。

第一步:确认问题 & 初步分析

在动手排查前,先要确认 Full GC 的严重程度。

1. 使用 jstat 命令

这是最快捷的命令行工具,用于实时监控 JVM 状态。

bash
# -gcutil: 显示GC相关的摘要信息
# <pid>: Java 进程ID
# <interval>: 采样间隔,单位毫秒
# <count>: 采样次数 (可选)
jstat -gcutil <pid> 1000 

输出解读:

plaintext
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
  0.00   0.00  25.31  45.12  96.78  94.89    152    0.521     5    0.231    0.752
  • O (Old): 老年代使用率。如果这个值持续增长并接近 100%,就要高度警惕。
  • FGC (Full GC Count): Full GC 的次数。如果这个数字在短时间内快速增长,就说明 Full GC 确实频繁。
  • FGCT (Full GC Time): Full GC 的总耗时。如果这个时间很长,说明每次 Full GC 的“Stop-the-World”暂停时间很长,对应用影响巨大。

2. 查看 GC 日志

GC 日志是最详细、最权威的证据。需要在 JVM 启动参数中开启:

bash
# Java 8
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/path/to/gc.log

# Java 9+
-Xlog:gc*:file=/path/to/gc.log:time,level,tags:filecount=10,filesize=100m

在日志中搜索关键字 [Full GC(full gc),可以清晰地看到每次 Full GC 的触发原因、时间和耗时。

常见触发原因:

  • Allocation Failure: 老年代空间不足,无法分配新对象。
  • System.gc(): 代码中显式调用了 System.gc()
  • Metadata GC Threshold: Metaspace(元空间)不足。

第二步:定位原因(核心环节)

确认了问题后,就需要深入分析原因。频繁 Full GC 的根本原因通常是 老年代(Old Generation)被过快占满

常见原因分类:

  1. 内存泄漏 (Memory Leak): 最常见的原因。对象在使用完后,由于仍然存在被 GCRoots 引用的路径,导致无法被垃圾回收,持续堆积在老年代。
  2. 大对象或长生命周期对象过多: 程序需要处理大量数据,创建了许多大对象(如大数组、大集合)或者生命周期很长的对象(如缓存、静态集合),这些对象直接进入或很快晋升到老年代。
  3. JVM 参数配置不当:
    • 堆内存设置过小 (-Xms, -Xmx)。
    • 新生代过小 (-Xmn),导致对象过早晋升到老年代。
    • Survivor 区过小,导致对象在经历少数几次 Minor GC 后就进入老年代。
  4. 代码中显式调用 System.gc(): 这是一个非常不好的实践,会强制触发 Full GC。

第三步:借助 Arthas 等工具进行深度排查

Arthas 是一个在线诊断工具,它可以在不重启应用的情况下,实时观察和修改应用状态,是排查此类问题的利器。

前提: 确保目标服务器已安装并启动了 Arthas。

场景一:排查内存泄漏/大对象

思路: 找到是哪些对象占用了最多的内存,然后追溯这些对象是谁创建的、被谁引用的。

1. 查看 JVM 整体内存情况

bash
# 进入 Arthas 命令行后
dashboard

dashboard 命令可以实时看到堆内存(eden, s0, s1, old)的使用情况。如果 old 区的使用率持续居高不下或稳步上涨,印证了我们的猜想。

2. 生成堆快照 (Heap Dump)

这是定位内存问题的“终极武器”。jmap 命令也可以做,但 Arthas 的 heapdump 命令更方便。

bash
heapdump /path/to/dump.hprof

注意: 生成堆快照会导致应用暂停(STW),请在业务低峰期或可接受的情况下操作。

分析堆快照:
dump.hprof 文件下载到本地,使用 MAT (Memory Analyzer Tool)JProfilerVisualVM 等工具进行分析。

  • MAT 分析步骤:
    1. 打开 dump.hprof 文件,选择 "Leak Suspects Report"(泄漏嫌疑报告),MAT 会自动分析并给出最可疑的泄漏点。
    2. 查看 "Dominator Tree"(支配树),按 retained size(保留大小)排序,找到占用内存最大的对象。
    3. 右键点击可疑的大对象,选择 "Path to GC Roots",查看它的引用链。通过引用链,你就能知道这个对象为什么没有被回收,从而定位到相关的业务代码。

3. Arthas 在线实时分析(无需 Heap Dump)

如果你不想或不能进行 Heap Dump,Arthas 提供了一些轻量级的在线排查命令。

  • jvm 命令查看GC信息

    bash
    jvm

    这个命令可以快速查看当前 JVM 的各项参数和内存、GC 统计,再次确认 Full GC 次数。

  • scsm 查看类信息
    如果你怀疑某个类有问题(比如一个叫 LocalCache 的类),可以查看它的信息。

    bash
    # 搜索所有加载的类
    sc *.LocalCache
    # 查看方法的详细信息
    sm com.example.LocalCache
  • watch 命令监控对象状态
    这是一个非常强大的命令。比如,你怀疑一个缓存 Map 在不停地增长。

    bash
    # 监控 MyCacheManager 类的 aCache 字段的大小
    # -x 2 表示展开2层
    watch com.example.MyCacheManager aCache size() -x 2

    通过这个命令,你可以实时看到缓存的大小变化,验证它是否在无限制增长。

  • profiler 命令进行火焰图分析
    虽然火焰图主要用于 CPU 分析,但它的 alloc 事件可以帮助我们找到 内存分配热点,即哪些代码在疯狂创建对象。

    bash
    
    # 启动 profiler,记录内存分配事件,持续30秒
    profiler start --event alloc --duration 30 -f /tmp/alloc.svg

    生成的 alloc.svg 火焰图会展示出哪些方法分配的内存最多。这对于定位“短时间内创建大量对象,导致 Young GC 压力过大,对象提前晋升”的场景非常有效。

场景二:排查 System.gc() 调用

思路: 找到是谁在调用 System.gc()

bash
# 追踪 System.gc() 方法的调用栈
stack java.lang.System gc

这个命令会打印出当前调用 System.gc() 的线程堆栈。你可以立刻看到是哪段代码、哪个第三方库触发了它。

解决方案:

  1. 如果是在自己的代码中,立即删除。
  2. 如果是第三方库,可以考虑升级版本或寻找替代方案。
  3. 作为最后的手段,可以在 JVM 启动参数中加入 -XX:+DisableExplicitGC 来禁止显式的 GC 调用。

场景三:排查 JVM 配置问题

思路: 检查当前的 JVM 参数是否合理。

bash
jvm

jvm 命令会列出所有 VM Flags,你可以检查 -Xmx, -Xms, -Xmn, -XX:SurvivorRatio 等参数是否符合预期。如果发现堆设置过小,或者新生代老年代比例不合理,就需要调整启动参数。


第四步:解决问题 & 验证

根据第二步和第三步的排查结果,进行相应的修复:

  1. 内存泄漏: 根据 MAT 的引用链分析,找到代码中的问题并修复(例如:关闭流、移除监听器、清空静态集合)。
  2. 大对象/长生命周期对象: 优化代码逻辑。例如:
    • 使用流式处理代替一次性加载所有数据。
    • 为缓存设置合理的过期策略和大小限制(如使用 Guava Cache 或 Caffeine)。
    • 检查 Session 超时时间是否过长。
  3. JVM 参数:
    • 适当增加总堆内存 (-Xmx)。
    • 调整新生代和老年代的比例。如果应用创建大量短生命周期的对象,可以适当调大新生代。
    • 考虑更换垃圾收集器,例如使用 G1 (-XX:+UseG1GC) 或 ZGC (-XX:+UseZGC),它们能更好地控制停顿时间。
  4. System.gc() 移除调用或使用 -XX:+DisableExplicitGC

验证:
修复并重新部署后,持续使用 jstat 或监控系统观察 FGC 次数是否显著下降,应用响应时间是否恢复正常。

总结:排查流程图

plaintext
graph TD
    A[应用响应慢/卡顿] --> B{确认是否为Full GC问题};
    B -- 是 --> C[使用 jstat/GC日志 确认FGC频率和耗时];
    C --> D{开始定位原因};
    D --> E[内存泄漏/大对象?];
    D --> F[显式调用System.gc()?];
    D --> G[JVM配置不合理?];
    
    E -- 是 --> H[Arthas: heapdump];
    H --> I[使用MAT分析dump文件];
    I --> J[找到泄漏对象和引用链];
    J --> K[修复代码逻辑];

    F -- 是 --> L[Arthas: stack java.lang.System gc];
    L --> M[找到调用方];
    M --> N[移除调用或加-XX:+DisableExplicitGC];

    G -- 是 --> O[Arthas: jvm];
    O --> P[检查-Xmx, -Xmn等参数];
    P --> Q[调整JVM启动参数];

    K --> R{发布验证};
    N --> R;
    Q --> R;
    R -- 问题解决 --> S[持续监控, 预防];
    R -- 未解决 --> D;

这个流程结合了标准工具和 Arthas 的强大功能,可以帮你高效、系统地解决绝大多数频繁 Full GC 的问题。记住,耐心和细致是解决这类问题的关键。

00:00
00:00