GC 算法:复制算法(Copying)
复制算法是针对标记-清除算法空间碎片化问题的第一个解决方案。它的核心思想很简单:将可用内存划分为大小相等的两块,每次只使用其中一块。当这块内存用完时,就将存活的对象复制到另一块上,然后再把已使用过的内存空间一次清理掉。
复制算法解决了碎片化问题,实现简单、运行高效,但代价是可用内存缩小为原来的一半。
算法原理
复制算法的工作流程:
- 将内存划分为 From 和 To 两块等大小的空间
- 只使用 From 区进行对象分配
- 当 From 区满时,触发 GC
- 将 From 区中存活的对象复制到 To 区
- 清理 From 区(整个区域一次性清理)
- 交换 From 和 To 的角色
复制操作的实现
新生代的应用
Java 堆的新生代采用复制算法,但做了优化:不是 1:1 划分,而是 Eden : Survivor = 8 : 1。
HotSpot VM 的复制算法实现:
复制算法的优点
- 无空间碎片:存活对象被紧密复制到 To 区,对象分配只需要移动指针
- 分配效率高:使用「指针碰撞」(bump-the-pointer)方式分配,速度快
- 实现简单:不需要维护复杂的空闲链表
复制算法的缺点
- 空间浪费:可用内存缩小为原来的一半
- 移动成本:所有存活对象都需要复制,如果存活率较高,复制成本较大
- 不适合老年代:老年代对象存活率高,复制代价太大
空间利用率优化
为解决空间浪费问题,JVM 采用「 Eden + 两个 Survivor」的设计:
Eden : Survivor = 8 : 1,意味着每次 Minor GC 后,新生代可用空间约为 90%- 只有 10% 的空间被「浪费」在 Survivor 区
适用场景
复制算法最适合对象存活率低的场景,这与分代收集理论中的「弱分代假说」吻合:大多数对象朝生夕灭,只有少数对象能熬过多次 GC。
新生代的 Minor GC 采用复制算法是完美的选择:新生代对象存活率低,复制成本小,空间利用率可以通过 Survivor 区优化到 90%。
对于老年代,对象存活率高,复制代价太大,需要使用标记-整理算法。