新生代适合标记-复制算法,因其对象“朝生夕灭”,每次Minor GC约90%以上死亡,仅需复制少量存活对象;内存分配快、无碎片、停顿可控,HotSpot通过Eden:Survivor=8:1:1设计将空间浪费压缩至约10%。

Java 中新生代使用的是标记-复制(Mark-Copy)算法,它不是直接“标记+复制”两步串行执行的独立算法,而是基于分块设计(如 Eden + 两个 Survivor 区)实现的一种高效、低停顿的内存回收机制。它的高效性不来自算法本身多精巧,而来自对对象生命周期特征的精准利用——绝大多数对象朝生暮死。
为什么新生代适合用标记-复制?
新生代中约 95% 的对象在一次 GC 后就不再被引用(即“短命”)。如果用标记-整理或标记-清除,需遍历并处理大量已死亡对象,效率低且易碎片化。而标记-复制只关注“活着的对象”,把它们集中复制到一块空闲区域,天然无碎片、清理彻底。
关键前提是:必须有额外空间容纳所有存活对象。新生代正是靠 Eden + From Survivor + To Survivor 的三区结构满足这一点——每次 GC 只使用其中两个区(如 Eden + From),将存活对象复制到 To 区,复制完成后角色互换。
一次 Minor GC 的实际流程(以 G1 之外的传统 Parallel/Serial 新生代为例)
假设当前使用 Eden 和 S0(From),S1(To)为空:
立即学习“Java免费学习笔记(深入)”;
- 扫描根对象:从栈帧局部变量、静态字段、JNI 引用等出发,标记 Eden 和 S0 中所有可达对象;
- 复制存活对象:将标记出的存活对象按年龄(经历 GC 次数)分流——年龄
- 清空原区域:Eden 和 S0 直接置空(无需逐个清理),S1 成为新的 From 区,下次 GC 时 S0 将作为 To 区;
- 更新对象引用:复制过程中,若对象被其他地方引用,GC 会更新该引用指向新地址(写屏障或卡表辅助)。
几个容易误解的关键点
不是先全量标记再全量复制:现代 JVM(如 HotSpot)采用“标记与复制交织”的方式。例如,在遍历对象图过程中,一旦发现某个对象存活,立即复制到 To 区,并更新其引用,避免重复扫描和冗余标记。
Survivor 区不是必须满才触发复制:只要 Eden 区满触发 Minor GC,就会启动复制流程;Survivor 空间不足时,多余存活对象直接晋升老年代(可能引发提前 Full GC)。
复制成本可控,因为只处理存活对象:哪怕 Eden 有 100MB,但只有 2MB 对象存活,JVM 实际只搬运这 2MB 数据 —— 这才是低延迟的核心。
如何观察和验证?
加 JVM 参数开启 GC 日志:
-XX:+PrintGCDetails -Xloggc:gc.log -XX:+PrintGCTimeStamps日志中看到类似:
[GC (Allocation Failure) [PSYoungGen: 102400K->24576K(114688K)] 102400K->24584K(378880K), 0.0123456 secs]其中 PSYoungGen 表示 Parallel Scavenge 收集器的新生代,102400K->24576K 是 GC 前后新生代使用量,差值即为被回收的空间;数值小但耗时短,正是复制算法高效的体现。


















