Minor GC 主要由 Eden 区空间不足触发,与 Survivor 区是否为空无关;次要路径包括 TLAB 分配失败后 Eden 全局分配仍失败、对象晋升时老年代空间不足等。Full GC 由老年代或元空间不足、显式调用 System.gc()、Minor GC 担保失败等任一条件触发。

Minor GC 触发条件:Eden 区满是主因,Survivor 满不触发
Minor GC 的核心触发点非常明确:Eden 区空间不足。只要新对象分配时发现 Eden 剩余空间不够,立刻触发——哪怕 Survivor 区还空着一大半,也不会因此提前 GC。
常见但容易被忽略的次要路径包括:
-
TLAB分配失败后,回退到 Eden 全局分配仍失败 → 触发 Minor GC - 对象年龄达到
-XX:MaxTenuringThreshold默认值 15(或 Survivor 区同龄对象占比超 50%)→ 晋升老年代,但若老年代没空间,这本身不触发 Minor GC,而是为后续 Full GC 埋雷 - 大对象直接分配进老年代失败时,会先尝试 Minor GC 来腾出空间再晋升;若 Minor GC 后仍无法容纳,才走 Full GC
Full GC 触发条件:老年代/元空间不足、显式调用或担保失败
Full GC 不是“堆满了才触发”,而是多个独立条件中的任意一个满足即可能启动:
- 老年代连续可用空间
- 大对象(如长数组)尝试直接进入老年代,但老年代没有足够连续空间 →
java.lang.OutOfMemoryError: Java heap space前大概率已触发 Full GC - 元空间(
Metaspace)超出-XX:MaxMetaspaceSize限制 → 常见于大量动态类生成(CGLib、Spring AOP、热部署未清理 ClassLoader) - 代码中写了
System.gc(),且未加-XX:+DisableExplicitGC→ JVM 接受建议并执行 Full GC - CMS 收集器发生
Concurrent Mode Failure,或 G1 的 Mixed GC 跟不上分配速度 → 回退为 Serial Old 或单线程 Full GC
性能影响范围:STW 时间差 10 倍起,且不可忽视元空间停顿
Minor GC 的 STW 时间通常在毫秒级,影响仅限年轻代(Eden + Survivor),对响应敏感型服务影响可控;Full GC 则必须扫描整个堆 + 元空间,STW 时间常达百毫秒甚至秒级。
关键差异在于:
- Minor GC 不处理老年代和元空间,所以不会卡住类加载、反射调用、JIT 编译等依赖元数据的操作
- Full GC 期间,所有线程冻结,包括 JIT 线程、JVM 内部线程、甚至
Unsafe.allocateMemory这类本地内存分配也会阻塞 - 某些 GC 算法(如 CMS)虽标榜“并发”,但初始标记和最终标记阶段仍需 STW,而 Full GC 时这些阶段会放大停顿
怎么快速定位是哪类 GC 在捣鬼
光看日志里的 “GC” 字样根本分不清,得盯住关键字段:
- 日志含
Pause Young(G1)、PSYoungGen(Parallel)、ParNew(CMS)→ 是 Minor GC - 出现
Pause Full、Full GC、Metaspace、Concurrent Mode Failure、Promotion Failed→ 基本锁定 Full GC - 用
jstat -gc <pid>查看YGC和FGC计数变化,再配合jstack抓取 STW 时刻线程栈,能确认是否真有长时间停顿 - 注意:-XX:+PrintGCDetails 输出中,如果看到老年代(
old或tenured)使用率突降,且时间戳旁标注Full,就是它了
真正难缠的不是 GC 类型本身,而是 Full GC 常由隐蔽问题引发:比如元空间泄漏导致的持续增长,或 Survivor 区过小迫使对象早晋升,最终把压力全堆给老年代——这些不会直接写在 GC 日志里,得结合堆直方图和类加载统计交叉验证。


















