外观
什么情况下会导致 full GC
⭐ 题目日期:
小红书 - 2024/11/11,2024/8/21 - 阿里
📝 题解:
在Java应用中,Full GC(全局垃圾回收)会暂停所有应用线程,对性能影响显著。以下是导致Full GC的常见情况及解决方案:
1. 老年代空间不足
- 原因:
- 长期存活对象填满老年代(默认年龄阈值15次Minor GC后晋升)。
- 大对象(如大数组)直接进入老年代,超过
-XX:PretenureSizeThreshold
设定值。 - 内存泄漏:对象无法回收,持续堆积在老年代。
- 排查工具:
jstat -gcutil
:观察老年代使用率(OU)。- 内存分析工具:MAT、VisualVM 查找泄漏对象。
- 解决方案:
- 增大老年代空间(
-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
设置不合理,导致老年代过小。 - 解决方案:根据应用对象生命周期调整分代比例。
- 如
排查步骤
- 监控GC日志:
-Xloggc:/path/to/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps
- 分析日志工具:
- GCViewer、GCEasy 可视化GC事件。
- 堆内存Dump分析:
jmap -dump:format=b,file=heap.hprof <pid>
优化建议
- 合理设置堆大小:避免过小导致频繁GC,过大增加停顿时间。
- 选择低延迟回收器:如G1、ZGC或Shenandoah。
- 代码层面优化:减少对象创建、及时释放资源、避免内存泄漏。
通过系统监控与代码优化,可显著减少Full GC频率,提升应用性能。