# 7. 什么情况下会发生 Full GC?
# 标准答案
Full GC 是指 JVM 在回收堆内存时,进行的全堆扫描和回收,通常涉及 老年代(Old Generation)、永久代/元空间(Metaspace)以及可能的年轻代(Young Generation)。Full GC 可能导致 Stop-The-World(STW),影响系统性能。
Full GC 触发的常见原因包括:
- 老年代空间不足(如晋升失败、老年代大对象过多)。
- 永久代/元空间溢出(类、方法过多导致
Metaspace
OOM)。 - GC 参数配置不当(如
System.gc()
、CMS/G1 垃圾回收失败)。 - 大对象直接分配到老年代(超过
PretenureSizeThreshold
)。 - SoftReference 被回收(JVM 试图回收软引用对象)。
- 晋升担保失败(Survivor 空间不足,无法晋升到老年代)。
- GC 触发策略调整(如
G1 Humongous Object
占用过多 Region)。
# 答案解析
# 1. Full GC 的工作流程
Full GC 是 JVM 最耗时 的 GC 操作,涉及的主要区域包括:
- 老年代(Old Generation):回收长期存活的对象。
- 永久代/元空间(PermGen/Metaspace):回收类、方法、反射数据等。
- 年轻代(Young Generation)(可能):部分 GC 机制在 Full GC 过程中会回收年轻代。
触发 Full GC 时,所有应用线程会被暂停(STW),并使用不同 GC 算法清理对象:
- Serial/Parallel GC:采用标记-清除-整理(Mark-Sweep-Compact)。
- CMS GC:失败时会触发 Serial Old GC(标记-清除)。
- G1 GC:全局并发标记失败后,触发混合回收或 Full GC。
# 2. 导致 Full GC 的常见原因
# (1)老年代空间不足
JVM 的对象晋升策略决定了年轻代存活的对象会晋升到老年代。如果老年代空间不足,会触发 Full GC:
- 大对象直接进入老年代(
PretenureSizeThreshold
设置过低)。 - 长期存活对象晋升(Survivor 空间装不下,直接晋升老年代)。
- 老年代被占满(内存泄漏、缓存过大等)。
- GC 触发阈值(
-XX:InitiatingHeapOccupancyPercent
触发 G1 GC)。
示例代码:
List<byte[]> list = new ArrayList<>();
while (true) {
list.add(new byte[1024 * 1024]); // 持续分配大对象,最终导致老年代 OOM
}
1
2
3
4
2
3
4
优化方案:
- 调整
-Xmx
和-Xms
,增加老年代空间。 - 优化对象生命周期,减少老年代内存占用。
# (2)Metaspace(元空间)溢出
- JDK 8+ 使用
Metaspace
代替PermGen
,存储类、方法、反射、动态代理等。 - 动态类过多(如大量
Spring
代理、ASM
生成的类)会导致 Metaspace OOM,引发 Full GC。
优化方案:
- 增加
Metaspace
大小:-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m
1 - 减少动态代理,优化
CGLIB
、ASM
代码。
# (3)System.gc()
主动触发
System.gc()
可能会强制触发 Full GC,特别是在 ExplicitGCInvokesConcurrent
关闭的情况下。
优化方案:
- 禁用显式 GC 调用:
-XX:+DisableExplicitGC
1 - 使用
-XX:+ExplicitGCInvokesConcurrent
(CMS/G1)避免 STW。
# (4)SoftReference 对象被回收
SoftReference(软引用)对象在内存紧张时可能触发 Full GC 来回收软引用对象。
SoftReference<byte[]> softRef = new SoftReference<>(new byte[10 * 1024 * 1024]);
System.gc(); // 可能会触发 Full GC
1
2
2
优化方案:
- 减少软引用使用,避免大对象 SoftReference 占用老年代。
# (5)晋升失败(Promotion Failure)
- Survivor 空间不足,年轻代存活对象直接晋升老年代。
- 老年代空间不足,无法容纳晋升对象,触发 Full GC。
优化方案:
- 调整
SurvivorRatio
提高 Survivor 空间:-XX:SurvivorRatio=8
1 - 调整
MaxTenuringThreshold
限制对象晋升:-XX:MaxTenuringThreshold=15
1
# (6)G1 GC 回收 Humongous Object
G1 GC 的大对象(Humongous Object) 会直接进入老年代,且占用多个 Region。当 Humongous Object 过多时,可能会触发 Full GC。
优化方案:
- 避免频繁分配超大对象:
-XX:G1HeapRegionSize=16m
1 - 调整 G1 Mixed GC 触发比例:
-XX:InitiatingHeapOccupancyPercent=45
1
# 3. 如何避免频繁 Full GC?
- 优化 GC 参数,减少老年代晋升
-XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=10
1 - 使用 G1/ZGC,减少 Full GC 影响
-XX:+UseG1GC -XX:G1HeapRegionSize=16m
1 - 限制
Metaspace
使用,避免 OOM-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m
1 - 避免
System.gc()
触发 Full GC-XX:+DisableExplicitGC
1 - 监控 GC 日志,分析 Full GC 频率
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log
1
# 4. 如何通过 GC 日志分析 Full GC?
使用 jstat
或 GC 日志
检测 Full GC 触发情况:
jstat -gcutil <pid> 1000 # 监控 GC 状态
jmap -histo <pid> # 查看对象分布
1
2
2
GC 日志示例:
[GC (Allocation Failure) [Full GC (Ergonomics) 512M->300M(1024M), 1.5s]
1
- Allocation Failure:因分配失败触发 Full GC
- 512M->300M(1024M):GC 后从 512M 降到 300M,最大 1024M
- 1.5s:Full GC 造成 1.5s STW,影响性能
# 深入追问
- 为什么
CMS
失败后会触发 Full GC? - 如何优化
Metaspace
以减少 Full GC? - 为什么
G1
避免了 Full GC,但仍可能触发? - 如何在
生产环境
中检测 Full GC 发生的频率?
# 相关面试题
System.gc()
一定会触发 Full GC 吗?- CMS 和 G1 如何避免 Full GC?
- 如何优化
Metaspace
,防止 OOM? SoftReference
在 Full GC 时会被清理吗?