垃圾回收算法
垃圾回收(Garbage Collection,简称GC)是 Java 虚拟机(JVM)自动管理内存的一种机制,用于回收不再使用的对象所占用的内存空间,从而避免内存泄漏和手动管理内存的复杂性。对于如何判定一个对象是否应该被回收,请参见之前的文章: Java引用类型
以下是几种常见的垃圾回收算法:
1. 标记-清除算法(Mark-Sweep)
基本原理
- 标记阶段:从每个 GC Roots 开始,递归遍历所有可达的对象,并将这些对象标记为存活。
- 清除阶段:遍历整个对象内存区域,清除未标记的对象(即垃圾对象)。
优点
- 算法简单,容易实现
缺点
- 效率问题:标记和清除过程效率不高,尤其是在对象数量较多的情况下。
- 空间碎片:标记清除后,会产生大量不连续的内存碎片,影响后续对象的分配。
标记清除算法是最基础的垃圾回收算法,后续的算法都是基于这种思路并对其不足进行改进而得到的。
2. 复制算法(Copying)
复制算法解决了内存碎片问题,并且这个算法也是分代算法的基础。
基本原理
将内存分为两个相等的区域,每次只使用其中一个区域。当该区域满时,将存活的对象复制到另一个区域,然后清除原区域的所有对象。
优点
- 执行效率高:只需要移动存活对象并更新指针,不需要进行复杂的标记和清除操作
- 无内存碎片:复制过程中会将存活对象连续地存储在新的区域,避免了内存碎片化
缺点
- 内存利用率低:每次只能使用一半的内存空间,内存空间浪费
- 对象存活数量较多时,复制性能较差
适用于新生代区域,因为新生代对象的生命周期较短,死亡率高,复制存活对象的开销相对较小。
3. 标记整理算法(Mark-Compact)
复制算法在对象存活率较高时,需要进行较多的复制操作,效率将会变低,更关键的是,如果不想浪费 50% 的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都 100% 存活的极端场景,所以在老年代一般不能直接使用复制算法。根据老年代的特点,有人提出了“标记-整理”算法。
基本原理
- 标记阶段:与“标记-清除”算法相同,标记所有存活的对象。
- 整理阶段:将所有存活的对象向内存的一端移动,然后更新指针,清除边界外的内存区域。
优点
- 避免内存碎片化:通过移动对象,使得内存空间更加连续,提高了内存利用率。
缺点
- 执行效率相对较低:需要移动对象并更新指针,操作较为复杂。
在老年代一般会用这种标记-整理算法。
4. 分代收集算法(Generational Collection)
基本原理
根据对象存活周期的不同将内存分为几块,一般是把 Java 堆分为新生代和老年代,根据各个年代的特点采用最适当的回收算法。
在新生代,每次 GC 时都发现有大量对象死去,只有少量存活,那就采用复制算法,只需付出少量存活对象的复制成本就可以完成收集。
在老年代,因对象存活率高,没有额外空间对它进行分配担保,就必须采用“标记-清除”或“标记-整理”算法来进行回收。
参考
《深入理解 Java 虚拟机》
必知必会JVM垃圾回收——对象搜索算法与回收算法