复制算法将堆内存划分为From和To两个相等区域,GC时仅遍历并复制From区存活对象至To区,清空From区后互换角色,从而实现无碎片、高吞吐的回收。

存活对象拷贝不是通用对象深拷贝,而是垃圾回收(GC)中“复制算法”(Copying GC)的核心操作——它专用于内存管理,目标是高效迁移当前仍被引用的活跃对象,同时自动清理死亡对象。关键不在于“怎么写一个 clone 方法”,而在于“如何在极短时间内、低开销地把一堆散落在老空间的对象,整块搬进新空间并更新所有引用”。
复制算法的基本逻辑
复制算法将堆内存划分为两个大小相等的半区(如 From 和 To),每次只使用其中一个。GC 触发时,遍历 From 区所有可达对象(即存活对象),将其连续、紧凑地复制到 To 区;复制完成后,角色互换,From 区直接清空(无需逐个清理)。这种设计天然解决内存碎片问题,且拷贝过程本身即完成标记与整理。
- 只处理存活对象,跳过所有已死亡对象,避免扫描和清理开销
- 复制时按顺序写入,To 区天然保持内存连续,后续分配可直接用指针 bump 方式,极快
- 对象移动后,必须修正所有指向它的引用(即“更新指针”),这是性能关键点
高效拷贝的关键实现机制
现代 JVM(如 HotSpot 的年轻代 Parallel Scavenge、G1 的部分 Evacuation 阶段)并不靠 Java 层的 clone() 或序列化,而是通过底层 C++ 实现的原子级内存操作:
-
批量内存拷贝:使用
memcpy或 CPU 指令(如 x86 的rep movsb)直接搬运对象原始字节,绕过任何 Java 方法调用或类型检查 - 卡片表(Card Table)或记忆集(Remembered Set)辅助:快速定位哪些老年代对象持有了年轻代中的引用,避免全堆扫描
- 写屏障(Write Barrier)配合:在对象字段赋值时拦截,提前记录跨代引用变更,确保复制时引用关系不丢失
- 转发指针(Forwarding Pointer)优化:对象被复制后,在原地址处写入一个指向新地址的指针;若其他对象再次访问该旧地址,可立即重定向,避免重复复制
为什么不能用常规深拷贝方法?
Java 应用层的深拷贝手段(如序列化、Cloneable、构造函数)完全不适用于 GC 场景:
- 序列化需反射、流封装、类校验,耗时高且无法处理循环引用或 native 资源
-
clone()要求类显式实现,且每个引用字段都得手动递归处理,GC 不可能为每个业务类生成定制逻辑 - 第三方库(如 BeanUtils)依赖运行时类型信息,引入额外对象分配和异常处理,违背 GC 对确定性与低延迟的要求
- 它们操作的是“语义对象”,而 GC 拷贝的是“内存镜像”——只要字节布局一致、引用字段被正确重定向,对象内容本身无需理解
实际效果与限制
复制算法的优势非常明显:停顿时间短、吞吐量高、无碎片。但它也有硬约束:
- 空间利用率仅 50%(两半区轮换),对大堆内存不够友好
- 当存活对象比例过高(>50%),To 区可能放不下,触发 Full GC 或退化为标记-整理
- 不适用于老年代——因此主流 GC(如 G1、ZGC)仅在年轻代或局部区域采用复制思想,而非全局复制

















