1、Young GC 的详细流程 (以最常见的 Parallel Scavenge / ParNew 收集器为例)
整个过程可以概括为: “标记-复制” (Mark-Copy) 。
第一步:暂停应用线程 (Stop-The-World) 和 GC Roots 枚举
- Young GC 开始时,JVM 会首先 暂停所有应用线程(Stop-The-World, STW),以确保在垃圾收集过程中对象引用关系不会发生变化。
- 然后,垃圾收集器从 GC Roots(如虚拟机栈局部变量表中的引用、静态变量、JNI引用等)开始,枚举出所有直接可达的存活对象。
第二步:标记 (Marking) 和 对象图遍历
- 收集器沿着 GC Roots 的引用链进行遍历,标记所有从 Roots 可达的对象为 存活对象。这个过程就像从树根开始,标记所有连接的树枝和树叶。
- 那些不可达的对象(即没有任何引用链与之相连)则被判定为 垃圾对象。
第三步:计算对象年龄和晋升判断
-
对于 Eden 和 当前使用的 Survivor (比如 S0,也称为 From 区) 中的每一个存活对象,收集器会检查其年龄。
- 对象的年龄:对象每在年轻代中经历一次 GC 并且存活下来,其年龄就增加 1。
-
根据年龄判断它应该被复制到哪里:
- 条件一:如果对象的年龄还没有达到 晋升年龄阈值 (
-XX:MaxTenuringThreshold设定,默认15),它会被复制到 另一个空的 Survivor 区 (比如 S1,也称为 To 区)。 - 条件二:如果对象的年龄已经达到阈值,或者 To 区(Survivor) 的空间不足以存放所有待复制的存活对象,则该对象会被直接晋升 (Promote) 到 老年代 (Old Generation) 。
- 特殊情况:如果对象是一个大对象(比如很大的数组),可能会直接分配在老年代(
-XX:PretenureSizeThreshold可设置阈值),因此不会参与Young GC。
- 条件一:如果对象的年龄还没有达到 晋升年龄阈值 (
第四步:复制 (Copying / Evacuation)
- 将 Eden 区和 From Survivor 区中所有存活的对象,按照第三步的判断,复制到 To Survivor 区或老年代。
- 这个复制过程是 紧凑 (Compacting) 的,意味着存活对象被紧密地排列在 To Survivor 区或老年代的新地址中,完全消除了内存碎片。
第五步:清理 (Clearing) 和 交换 (Swapping)
- 清理:在复制完所有存活对象后,Eden 区和刚才使用的 From Survivor 区中剩下的就全都是垃圾对象了。收集器会直接清空整个 Eden 区和 From Survivor 区。
- 交换:清空后,S0 和 S1 的角色会发生交换。本次GC的 To Survivor 区 (S1) 在下次GC时将成为 From Survivor 区。如此反复循环。
第六步:恢复应用线程
- 所有工作完成后,JVM 恢复所有被暂停的应用线程,程序继续运行。新创建的对象又会开始在新的 Eden 区中分配。