Skip to content

什么情况下会导致 full GC

约 822 字大约 3 分钟

JVM小红书阿里

2025-03-14

⭐ 题目日期:

小红书 - 2024/11/11,2024/8/21 - 阿里

📝 题解:

在Java应用中,Full GC(全局垃圾回收)会暂停所有应用线程,对性能影响显著。以下是导致Full GC的常见情况及解决方案:


1. 老年代空间不足

  • 原因
    • 长期存活对象填满老年代(默认年龄阈值15次Minor GC后晋升)。
    • 大对象(如大数组)直接进入老年代,超过 -XX:PretenureSizeThreshold 设定值。
    • 内存泄漏:对象无法回收,持续堆积在老年代。
  • 排查工具
    • jstat -gcutil:观察老年代使用率(OU)。
    • 内存分析工具:MATVisualVM 查找泄漏对象。
  • 解决方案
    • 增大老年代空间(-Xmx 调整总堆大小,-XX:NewRatio 调整新生代比例)。
    • 优化代码,减少大对象分配或缩短对象生命周期。
    • 修复内存泄漏(如关闭未释放的资源、清理静态集合)。

2. 永久代/元空间不足

  • 原因
    • 加载过多类(动态代理、反射生成类)。
    • 类加载器泄漏(如OSGi、热部署场景)。
  • 排查工具
    • jstat -gc 查看元空间使用(MC/MU列)。
    • -XX:+TraceClassLoading 追踪类加载。
  • 解决方案
    • 增大元空间:-XX:MaxMetaspaceSize=512m
    • 优化类生成逻辑(如缓存动态代理类)。
    • 重启应用或修复类加载器泄漏。

3. 显式调用 System.gc()

  • 原因
    • 代码中直接调用 System.gc()Runtime.getRuntime().gc()
  • 解决方案
    • 避免显式调用GC方法。
    • 若必须调用,通过 -XX:+DisableExplicitGC 禁用(可能影响NIO的Direct Buffer回收)。

4. 内存分配失败

  • 原因
    • 晋升失败:Minor GC后存活对象无法放入Survivor区,且老年代空间不足。
    • 大对象分配失败:老年代无法容纳大对象。
  • 解决方案
    • 调整新生代大小(-Xmn)或 Survivor 区比例(-XX:SurvivorRatio)。
    • 增大堆内存或优化对象分配策略(避免突然的大对象分配)。

5. GC回收器策略触发

  • CMS回收器
    • 并发模式失败:回收速度跟不上对象分配速度,触发Full GC。
    • 解决方案:降低触发CMS的阈值(-XX:CMSInitiatingOccupancyFraction=70)。
  • G1回收器
    • 疏散失败:无足够Region容纳复制对象。
    • 解决方案:增大堆内存或调整 -XX:G1ReservePercent

6. 其他原因

  • 代码逻辑问题
    • 频繁创建短生命周期的大对象(如数据库查询返回大量数据)。
    • 解决方案:分页查询、缓存结果或优化数据分批处理。
  • JVM参数配置不当
    • -XX:NewRatio 设置不合理,导致老年代过小。
    • 解决方案:根据应用对象生命周期调整分代比例。

排查步骤

  1. 监控GC日志
-Xloggc:/path/to/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps
  1. 分析日志工具
    1. GCViewerGCEasy 可视化GC事件。
  2. 堆内存Dump分析
jmap -dump:format=b,file=heap.hprof <pid>

优化建议

  • 合理设置堆大小:避免过小导致频繁GC,过大增加停顿时间。
  • 选择低延迟回收器:如G1、ZGC或Shenandoah。
  • 代码层面优化:减少对象创建、及时释放资源、避免内存泄漏。

通过系统监控与代码优化,可显著减少Full GC频率,提升应用性能。