Java Survivor区学习笔记

2026/3/16 笔记Java

# 一、Survivor区核心设计思想

Survivor区(幸存者区)是Java虚拟机(JVM)堆内存中年轻代的重要组成部分,核心设计思想围绕「高效垃圾回收、避免内存碎片、筛选长期存活对象」展开,本质是为了配合年轻代的复制回收算法,减少老年代的内存压力,提升整个JVM的运行效率。

补充基础:年轻代主要存放刚创建的短生命周期对象,默认由 Eden区(新对象分配区)+ 2个Survivor区(S0/S1,也叫From/To区)组成,三者默认大小比例为8:1:1,这是基于“98%的对象都是朝生夕死”的特性设计的,Survivor区就是用来“筛选”出那2%可能长期存活的对象,避免它们过早进入老年代(老年代回收效率极低)。

# 二、为什么必须设计两个Survivor区?

年轻代的垃圾回收(Minor GC)采用「复制算法」,核心逻辑是:将存活对象从一个区域复制到另一个区域,然后清空原区域,这样能快速回收内存,且不会产生内存碎片(内存碎片会导致后续分配大对象时,即使总内存足够,也无法找到连续空间而触发频繁GC)。

两个Survivor区的存在,正是为了让复制算法能高效、简单地运行,核心作用是「轮流作为“源区”和“目标区”」,实现“一次复制、一次交换”的循环逻辑,具体原因有2点:

  1. 保证复制的“干净性”:每次Minor GC时,存活对象会从「Eden区 + 当前使用的Survivor区(源区)」复制到「空闲的Survivor区(目标区)」,目标区始终是空的,能确保复制过去的存活对象不会和旧对象混合,避免内存碎片,也无需额外判断“哪些是存活对象”,提升效率。

  2. 实现对象年龄统计:对象每在两个Survivor区之间复制一次(即经历一次Minor GC),年龄就会增加1,当年龄达到阈值(默认15次,由JVM参数控制),就会晋升到老年代。两个区的轮换,能清晰记录对象的存活次数,筛选出真正长期存活的对象,避免老年代被短生命周期对象占用而频繁触发Full GC(Full GC会导致程序卡顿)。

# 三、为什么不设计一个Survivor区?

如果只设计一个Survivor区,会彻底破坏复制算法的逻辑,带来两个无法解决的问题,初学者可以简单理解为“顾此失彼”:

  1. 复制时会破坏原对象:Minor GC需要先判断Eden区和Survivor区的存活对象,再复制到目标区。如果只有一个Survivor区,复制时会把存活对象复制到同一个区,此时原存活对象还没被清空,会和新复制的对象混合,导致JVM无法区分“已复制的存活对象”和“未判断的对象”,要么漏回收,要么误回收,逻辑彻底混乱。

  2. 无法统计对象年龄,且产生内存碎片:没有空闲的目标区,复制后的存活对象会零散分布在Survivor区,产生内存碎片;同时,无法记录对象经历了多少次Minor GC,要么让对象过早进入老年代(增加老年代压力),要么让对象一直留在Survivor区(占满空间,导致后续对象无法存放),最终导致GC效率暴跌,程序运行卡顿。

简单类比:一个Survivor区就像“只有一个房间,既要住人,又要打扫卫生”,打扫时人只能暂时站在房间里,会挡住打扫的地方;两个Survivor区就像“两个房间,人先搬到空房间,再彻底打扫原房间”,高效又干净。

# 四、Survivor区实际运行示例

结合默认的Eden:S0:S1 = 8:1:1比例,用“步骤+通俗解释”说明Survivor区的运行过程,忽略复杂参数,只看核心流转:

# 初始状态

Eden区(8份):可分配新对象;S0(1份):空闲;S1(1份):空闲(初始时两个Survivor区都是空的,第一次GC后才会有角色区分)。

# 步骤1:分配对象(Eden区满,触发第一次Minor GC)

  1. 程序频繁创建新对象(比如new String()、new 数组),所有新对象都分配到Eden区;

  2. 当Eden区被占满,JVM触发第一次Minor GC,开始清理Eden区的垃圾对象(不再被引用的对象);

  3. 把Eden区中存活的对象,复制到S0(此时S0是“目标区”,S1仍然空闲);

  4. 复制完成后,清空Eden区,此时S0存放存活对象(年龄=1),S1为空,角色定义:S0=From区(下次GC的源区),S1=To区(下次GC的目标区)。

# 步骤2:第二次Minor GC(Eden区再次满)

  1. 程序继续创建对象,Eden区再次被占满,触发第二次Minor GC;

  2. 清理Eden区和From区(S0)的垃圾对象,把两个区域中存活的对象,复制到To区(S1);

  3. 复制时,所有存活对象的年龄+1(此时存活对象年龄=2);

  4. 复制完成后,清空Eden区和S0(From区),角色交换:S1=From区,S0=To区(下次GC时反向复制)。

# 步骤3:循环流转,直到对象晋升老年代

  1. 每次Eden区满,触发Minor GC,存活对象就从「Eden+当前From区」复制到「当前To区」,年龄+1,然后交换From/To区的角色;

  2. 当某个对象的年龄达到阈值(默认15次,即经历15次Minor GC后仍然存活),就会直接晋升到老年代,不再在两个Survivor区之间流转;

  3. 特殊情况:如果某次复制时,To区放不下所有存活对象,多余的存活对象会直接晋升到老年代(避免Survivor区溢出)。

# 示例简化总结

Eden区生对象,Minor GC后存活对象去Survivor,两个Survivor区轮流“存存活对象、空着等复制”,对象住得越久(年龄越大),最终搬到老年代“养老”,全程不产生碎片,还能高效筛选长期对象。

# 五、初学者重点总结

  • Survivor区核心作用:配合复制算法,避免内存碎片,统计对象年龄,筛选长期存活对象。

  • 双区设计:为了实现“源区→目标区”的复制+交换,让GC高效、简单运行,解决单区的逻辑混乱和碎片问题。

  • 运行核心:Eden区满触发Minor GC,存活对象在两个Survivor区间复制、年龄递增,达到阈值晋升老年代。