JVM的垃圾回收机制(一次完整的GC流程)
一、GC 触发前提
GC 由 JVM 自动触发,核心触发条件:
- 新生代空间不足:对象创建时若 Eden 区满,触发 Minor GC(仅回收新生代);
- 老年代空间不足:Minor GC 后对象晋升到老年代,若老年代满,触发 Major GC/Full GC(回收老年代 + 新生代,可能含元空间);
- 元空间不足:类加载时元空间(存储类信息、常量等)满,触发元空间清理;
- 主动调用:代码中调用
System.gc()
(JVM 可忽略,仅为建议)。
二、完整 GC 流程(分代收集)
阶段 1:Minor GC(新生代回收)
新生代分为 Eden 区(80%)和两个 Survivor 区(S0、S1,各 10%),对象优先在 Eden 区创建,流程如下:
- 对象分配:新对象先存入 Eden 区,当 Eden 区满时,触发 Minor GC;
- 存活对象标记:用 可达性分析法 标记 Eden 区和 S0 区中 “存活的对象”(从 GC Roots 可达的对象);
- 存活对象复制:将标记出的存活对象,按年龄(每经历一次 Minor GC 年龄 + 1)复制到 S1 区:
- 年龄未达阈值(默认 15):直接复制到 S1 区;
- 年龄达阈值:晋升到 老年代(大对象可能直接进入老年代,避免频繁复制);
- 清空无效对象:清空 Eden 区和 S0 区的无效对象(未被标记的垃圾);
- 角色互换:S0 和 S1 区互换角色(下次 Minor GC 时,S1 作为 “from 区”,原 S0 作为 “to 区”),保证每次只有一个 Survivor 区存存活对象。
特点:Minor GC 频率高、耗时短(新生代对象存活时间短,复制成本低),采用 复制算法。
阶段 2:Major GC/Full GC(老年代 + 新生代回收)
当老年代空间不足(如 Minor GC 后晋升的对象超过老年代剩余空间),触发 Major GC(有时也叫 Full GC,若同时回收新生代则为 Full GC),流程如下:
- 全局标记:用 可达性分析法 标记老年代、新生代中所有存活对象(GC Roots 遍历全堆);
- 清除无效对象:
- 老年代采用 标记 - 清除算法 或 标记 - 整理算法:
- 标记 - 清除:直接删除未标记的垃圾,效率高但会产生内存碎片;
- 标记 - 整理:将存活对象向一端移动,然后清除边界外的垃圾,无碎片但耗时更长;
- 新生代同步执行一次 Minor GC(复制算法),清空无效对象;
- 老年代采用 标记 - 清除算法 或 标记 - 整理算法:
- 内存整理(可选):若老年代用标记 - 整理算法,移动存活对象后,更新对象引用地址(避免空指针);
- 元空间清理:若元空间(永久代)满,清理未使用的类信息(如类加载器回收、类无引用),释放元空间内存。(注:元空间内存不足时采用的是FullGC算法)
阶段 3:STW(Stop The World)与并发优化
GC 执行时会暂停应用线程(STW),避免线程操作与 GC 冲突,主流 JVM 通过优化减少 STW 时间:
- Minor GC 的 STW:仅暂停线程短暂时间(毫秒级),复制存活对象时锁定新生代;
- 并发 GC(如 G1、ZGC):将标记阶段拆分为 “初始标记”“并发标记”“最终标记”,仅初始和最终标记需 STW,并发标记阶段与应用线程并行,大幅缩短 STW 时间。
三、完整流程总结(简化版)
- 触发:Eden 区满 → 触发 Minor GC;老年代满 → 触发 Major/Full GC;
- Minor GC:标记 Eden+S0 存活对象 → 复制到 S1(或晋升老年代)→ 清空 Eden+S0 → S0/S1 互换;
- Major/Full GC:全堆标记存活对象 → 老年代清除 / 整理垃圾 + 新生代 Minor GC → 元空间清理;
- 恢复:STW 结束,应用线程继续执行,内存分配恢复。
扩展与补充:
1、Minor GC和Major GC/Full GC的区别
Minor GC 快:
新生代用复制算法,
Major GC 慢:
老年代用标记 - 清除 / 整理算法
Full GC 最慢:
需同时处理新生代和老年代,STW 时间可能长达几秒,严重影响应用响应性
2、标记 - 清除算法 或 标记 - 整理算法在老年代什么时候执行?
- 标记 - 清除:老年代中多用于低延迟收集器(如 CMS),优先保证回收速度,通过定期整理缓解碎片。
- 标记 - 整理:老年代中多用于吞吐量优先或稳定性要求高的收集器(如 Serial Old、Parallel Old、G1),通过消除碎片保证长期内存分配效率。
3、元空间与永生代的区别?
JDK 8 用元空间替代永久代,主要差异如下:
特性 | 永久代(JDK 7 及之前) | 元空间(JDK 8 及之后) |
---|---|---|
内存区域 | 属于 JVM 堆内存的一部分 | 属于本地内存(Native Memory) |
大小限制 | 默认有固定大小(可通过 -XX:PermSize 和 -XX:MaxPermSize 调整) | 默认无上限(受本地内存大小限制,可通过 -XX:MaxMetaspaceSize 限制) |
内存溢出风险 | 容易因类过多导致 PermGen OOM | 内存溢出风险降低(除非本地内存耗尽) |
垃圾回收 | 依赖 JVM 堆的 GC 机制 | 独立的元空间 GC 机制,当元空间不足时触发 |