复制算法适合新生代,因其利用对象高死亡率(存活率仅1%–5%),仅搬运少量存活对象,实现低延迟、无碎片、高吞吐的minor gc。

复制算法在新生代高效回收垃圾,核心在于利用“绝大多数对象朝生夕死”这一经验规律,只处理少量存活对象,避免遍历和整理大量内存。它不追求100%回收所有垃圾,而是用空间换时间,实现低延迟、无碎片、高吞吐的Minor GC。
为什么复制算法适合新生代
新生代中对象存活率通常低于10%,甚至只有1%–5%。复制算法只需把这极少数存活对象搬走,其余95%以上空间可一次性清空,效率远高于标记-清除或标记-整理。
- 无需逐个扫描判断是否回收,只搬运“活”的
- 搬运后目标区域天然紧凑,彻底消除内存碎片
- Eden + 两个Survivor区结构支持快速翻转,GC过程可高度并行化(如Parallel Scavenge、ParNew)
- 配合TLAB(线程本地分配缓冲),对象分配几乎无锁,进一步减少GC前的争用开销
实际执行流程(以HotSpot为例)
一次典型的Minor GC按以下步骤完成:
- 触发条件:Eden区满(或显式调用System.gc(),但不推荐)
- 暂停所有应用线程(STW),从GC Roots出发,标记Eden和当前From Survivor中所有可达对象
- 将标记出的存活对象按年龄(经历GC次数)分类:年龄未达阈值的复制到To Survivor;达到阈值(默认15)或大对象直接晋升老年代
- 清空Eden和From Survivor整个区域(位图清零或指针归零,极快)
- 交换From/To角色,为下次GC准备
关键设计保障高效性
几个底层机制让复制真正“高效”,不只是理论快:
- Survivor区大小可调:通过-XX:SurvivorRatio(如设为8,表示Eden:Survivor=8:1:1)匹配实际存活率,避免To区溢出导致提前晋升
- 动态年龄阈值:JVM会统计各年龄对象总大小,若某年龄及更小的对象累计占To区一半以上,就将该年龄设为新晋升阈值,防止“幸存者通胀”
- 大对象直入老年代:通过-XX:PretenureSizeThreshold设定阈值,避免大数组反复复制消耗带宽
- GC日志反馈闭环:开启-XX:+PrintGCDetails后,观察“Desired survivor size”与实际使用量差距,可反推是否需调优Survivor比例或MaxTenuringThreshold
注意边界情况
高效不等于万能。以下情形会削弱复制优势:
- Survivor空间不足 → 多余存活对象被迫直接晋升,可能引发老年代压力上升甚至Full GC
- 对象平均存活时间变长(如缓存误用、流式处理堆积)→ To区频繁填满,晋升加速
- 堆外内存或直接字节缓冲区(DirectByteBuffer)未及时清理 → 表面GC正常,实则元空间或本地内存持续增长
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











