触发 Full GC 的常见条件有哪些?
触发 Full GC(Full Garbage Collection)是 Java 性能调优和故障排查中非常核心的问题。Full GC 会对整个堆内存(包括年轻代、老年代,以及元空间/永久代)进行垃圾回收。由于它会引发显著的 STW(Stop-The-World,暂停所有应用线程),因此在生产环境中通常需要尽量避免。
以下是触发 Full GC 的常见条件,可以归纳为以下几大类:
1. 老年代空间不足 (Old Generation Exhaustion)
这是触发 Full GC 最常见的原因。当对象从年轻代晋升到老年代,或者大对象直接分配到老年代,导致老年代剩余空间不足时,就会触发 Full GC。
- 大对象直接进入老年代: 代码中创建了非常大的数组或字符串,超过了
-XX:PretenureSizeThreshold设置的阈值,直接在老年代分配,导致老年代迅速填满。 - 长期存活的对象进入老年代: 系统的并发量大,对象存活时间长,熬过了多次 Minor GC(达到
-XX:MaxTenuringThreshold),大量进入老年代。 - 内存泄漏: 某些对象不再使用,但由于被 GC Roots 强引用导致无法回收,老年代空间持续上涨,最终撑爆。
2. 元空间 / 永久代空间不足 (Metaspace / PermGen Exhaustion)
- JDK 1.7 及以前(永久代): 当系统中加载的类、反射的类、调用的方法过多,或者常量池过大时,会导致永久代(PermGen)空间不足,触发 Full GC。
- JDK 1.8 及以后(元空间): 永久代被移除,替换为使用本地内存的元空间(Metaspace)。如果程序动态生成了大量的类(例如使用了大量的 CGLib 动态代理、JSP 编译、OSGi 模块化等),且达到了
-XX:MaxMetaspaceSize设置的上限,同样会触发 Full GC 来卸载不用的类。
3. 空间分配担保失败 (Promotion Failure)
在发生 Minor GC(年轻代 GC)之前,JVM 会评估老年代的连续空闲空间是否足够。
- 机制: JVM 会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间。
- 触发条件: 如果不大于,JVM 会检查是否允许担保失败(
HandlePromotionFailure)。如果允许,会进一步检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小。- 如果小于历史平均大小,说明这次大概率也装不下,为了安全起见,会直接提前触发一次 Full GC。
- 如果大于,则尝试进行 Minor GC。但如果这次存活的对象意外地多(超过了历史平均值),导致老年代还是装不下(即担保失败),就会在 Minor GC 之后再追加一次 Full GC。
4. 显式调用 GC (Explicit GC)
代码中或通过工具人为触发了垃圾回收。
- 代码调用: 应用程序中显式调用了
System.gc()或Runtime.getRuntime().gc()。这只是建议 JVM 执行 Full GC,JVM 通常会采纳。- 应对方案: 生产环境中通常会配置 JVM 参数
-XX:+DisableExplicitGC来屏蔽代码中的显式调用。
- 应对方案: 生产环境中通常会配置 JVM 参数
- RMI 机制: 如果系统使用了 RMI(远程方法调用),RMI 的分布式垃圾回收机制(DGC)会默认每小时(或者更短时间)定期调用一次
System.gc()。 - 监控工具: 使用
jmap -histo:live、jcmd导出堆快照等排查工具时,会主动触发 Full GC 以确保统计的是真正的存活对象。
5. 并发模式失败 (Concurrent Mode Failure - 针对 CMS 垃圾收集器)
这是 CMS 收集器特有的问题。CMS 是一款并发收集器,在它进行老年代垃圾回收的过程中,应用线程仍在运行,这期间产生的新的存活对象仍然需要放入老年代。
- 触发条件: 如果在 CMS 垃圾回收还没执行完的时候,老年代的空间就已经被新晋升的对象填满了,就会发生
Concurrent Mode Failure。 - 后果: 此时 JVM 会退化使用 Serial Old 收集器,暂停所有应用线程(STW),进行单线程的 Full GC,导致非常长的停顿时间。
- 应对方案: 可以通过调低
-XX:CMSInitiatingOccupancyFraction(例如降到 70%)来让 CMS 更早地启动,预留足够的空间。
6. G1 垃圾收集器的特定触发条件
G1 收集器的目标是避免 Full GC(它有自己的 Mixed GC),但在极端情况下也会触发退化版的 Full GC(Serial 收集):
- Evacuation Failure(转移失败): 在进行垃圾回收(拷贝存活对象到新的 Region)时,如果没有足够的空闲 Region 来存放存活的对象。
- Humongous Allocation 失败: 尝试分配巨大的对象(大于 Region 大小的一半),但在整个堆中找不到足够的连续 Region 时。
总结与排查建议
当生产环境频繁发生 Full GC 时,通常意味着系统存在内存泄漏、JVM 参数配置不合理或代码中存在大对象滥用。
排查第一步:
- 加上 JVM 参数开启 GC 日志:
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log(JDK 8)或-Xlog:gc*=info:file=gc.log(JDK 9+)。 - 在发生 OOM 或 Full GC 前自动导出堆内存:
-XX:+HeapDumpOnOutOfMemoryError。 - 使用工具(如 JProfiler、MAT、Arthas)分析 Heap Dump 文件,找出占用内存最多的对象及其引用链。