JVM中对象如何从伊甸园区(Eden)进入幸存者区(Survivor)

JVM中对象如何从伊甸园区(Eden)进入幸存者区(Survivor)

在 JVM 的垃圾回收(GC)过程中,对象从 伊甸园区(Eden) 进入 幸存者区(Survivor) 的过程是 Minor GC(年轻代垃圾回收) 的核心机制。下面详细解释这一过程的步骤、条件和底层原理。


1. 对象分配流程(进入 Eden 区)

  • 新对象优先分配在 Eden 区
    当程序通过 new 关键字创建对象时,JVM 会尝试在 Eden 区 分配内存。
    • 如果 Eden 区空间不足,触发 Minor GC(年轻代垃圾回收)。
    • 如果 Minor GC 后仍不足,可能直接晋升到老年代(取决于 JVM 配置)。

2. Minor GC 触发时:对象从 Eden → Survivor

Eden 区满 时,JVM 会触发 Minor GC,存活的对象会被转移到 Survivor 区。具体步骤如下:

步骤 1:标记存活对象

  • 可达性分析(GC Roots)
    JVM 从 GC Roots(如线程栈帧的局部变量、静态变量等)出发,标记所有 可达对象(存活对象)。
    • Eden 区中 被引用 的对象会被标记为存活。
    • 未被引用 的对象(垃圾)会被回收。

步骤 2:复制存活对象到 Survivor 区

  • 存活对象从 Eden 复制到 Survivor(To 区)
    • 所有 Eden 区存活的对象会被复制到 To Survivor(S0 或 S1)
    • 对象年龄(Age) +1:每经历一次 Minor GC,对象的年龄增加 1(存储在对象头中)。
    • Survivor 区采用“复制算法”
      • 每次 Minor GC 时,存活对象会被复制到 空的 Survivor 区(From 和 To 交替使用)。
      • 例如:
        • 第一次 GC:Eden → S1(假设 S0 为空)。
        • 第二次 GC:Eden + S1 → S0(S1 变为 From,S0 变为 To)。

步骤 3:清空 Eden 区和 From Survivor

  • Eden 区和 From Survivor 被清空
    • 复制完成后,Eden 区和 当前的 From Survivor(存放上次存活对象的 Survivor)会被完全清空。
    • From 和 To 角色互换
      • 原来的 To Survivor 变成下一轮的 From Survivor
      • 原来的 From Survivor 变成下一轮的 To Survivor(等待接收新存活对象)。

3. 对象晋升老年代的条件

如果对象在 Survivor 区存活足够长时间,最终会晋升到 老年代(Old Generation)。晋升条件包括:

  1. 年龄阈值(MaxTenuringThreshold)
    • 默认 -XX:MaxTenuringThreshold=15(经历 15 次 Minor GC 后晋升)。
    • 可通过 JVM 参数调整,例如 -XX:MaxTenuringThreshold=5
  2. 动态年龄计算
    • 如果某一年龄(如 age=2)的对象总大小超过 Survivor 区的一半,则 ≥该年龄的对象直接晋升(避免 Survivor 区溢出)。
  3. Survivor 区空间不足
    • 如果 Survivor 区无法容纳所有存活对象,部分对象会直接晋升到老年代(即使年龄未达标)。

4. 示例流程(Eden → Survivor → Old)

假设初始状态:

  • Eden 区:存放新对象 A, B, C
  • From Survivor(S0):空。
  • To Survivor(S1):空。

第一次 Minor GC

  1. 标记存活对象:假设 A, C 存活,B 被回收。
  2. 复制到 To Survivor(S1)A, C 被复制到 S1,年龄 +1(A.age=1, C.age=1)。
  3. 清空 Eden 和 From Survivor:Eden 和 S0 被清空。
  4. 角色互换
    • S1 变为 From Survivor(存放存活对象)。
    • S0 变为 To Survivor(等待下次 GC)。

第二次 Minor GC

  1. Eden 区新分配对象 D, E, F
  2. 标记存活对象:假设 D, F 存活,E 被回收;S1 中的 A, C 仍然存活。
  3. 复制到 To Survivor(S0)A, C, D, F 复制到 S0,年龄 +1(A.age=2, C.age=2, D.age=1, F.age=1)。
  4. 清空 Eden 和 From Survivor(S1)
  5. 角色互换
    • S0 变为 From Survivor
    • S1 变为 To Survivor

后续晋升

  • 如果 A.age=15(默认阈值),则 A 会在下次 GC 时晋升到 老年代

5. 关键 JVM 参数

参数 作用 建议值
-XX:SurvivorRatio Eden 区与单个 Survivor 区的比例(默认 8)。 -XX:SurvivorRatio=6(增大 Survivor)。
-XX:MaxTenuringThreshold 对象晋升老年代的年龄阈值(默认 15)。 -XX:MaxTenuringThreshold=5(减少年轻代停留)。
-XX:+PrintGCDetails 打印 GC 详情(观察 Eden/Survivor 变化)。 调试时启用。
-XX:PretenureSizeThreshold 大对象直接进入老年代的阈值(默认 0)。 -XX:PretenureSizeThreshold=1M(避免大对象占用 Eden)。

6. 常见问题

Q1:为什么 Survivor 区要分为 From 和 To?

  • 复制算法(Copying Algorithm)要求
    • 每次 Minor GC 时,存活对象被复制到 空的 Survivor 区(To),然后清空原来的 Survivor 区(From),避免内存碎片。
    • From 和 To 交替使用,确保始终有一个 Survivor 区是空的。

Q2:如果 Survivor 区空间不足怎么办?

  • 直接晋升老年代
    • 如果 Survivor 区无法容纳所有存活对象,部分对象会 直接进入老年代(即使年龄未达标)。
    • 可通过 -XX:SurvivorRatio 调整比例,或增大年轻代总大小(-Xmn)。

Q3:如何监控对象年龄分布?

  • 使用 jstat-XX:+PrintTenuringDistribution
    jstat -gc  1000  # 每 1 秒输出 GC 数据
    
    -XX:+PrintTenuringDistribution  # 打印对象年龄分布
    

总结

  • Eden → Survivor:Minor GC 时,存活对象从 Eden 复制到 To Survivor,年龄 +1。
  • Survivor 区交替使用:From 和 To 在每次 GC 后交换角色。
  • 晋升老年代:年龄 ≥ MaxTenuringThreshold 或 Survivor 空间不足时晋升。
  • 调优关键:通过 SurvivorRatioMaxTenuringThreshold 优化对象生命周期。

通过 jvisualvmGC 日志 可以直观观察对象在 Eden/Survivor 区的流动情况。

你可能感兴趣的:(JVM,jvm,java)