JVM相关

64 阅读2分钟

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 区中分配。