# 28. 常见GC算法有哪些?标记-清除、复制、标记-整理有何区别?
# 标准答案
Java 中的常见垃圾回收算法有 标记-清除算法、复制算法、标记-整理算法,它们分别以不同的方式回收不再使用的对象。
- 标记-清除算法:标记所有需要回收的对象,然后清除它们,存在碎片问题。
- 复制算法:将内存分为两个区域,使用一个区域存储活动对象,回收时将活动对象复制到另一区域,减少碎片。
- 标记-整理算法:标记需要回收的对象,接着将存活对象整理到一端,清除未使用的内存,避免碎片问题。
# 答案解析
# 1. 标记-清除算法(Mark-and-Sweep)
标记-清除算法是最基础的垃圾回收算法,其工作原理如下:
- 标记阶段:遍历所有对象,标记那些可达的对象(即存活的对象)。这通常通过可达性分析来完成,从GC Root出发,沿着引用链遍历标记可达的对象。
- 清除阶段:清除所有未标记的对象,即不可达的对象。
问题:标记-清除算法的一个明显问题是 碎片化,因为回收过程中不会移动对象,只是标记和清除对象,这会导致内存中存留大量空闲空间(即内存碎片)。随着时间的推移,这会影响内存的使用效率,甚至导致无法分配足够的内存。
# 2. 复制算法(Copying)
复制算法通过将内存分为两部分,分别为 From Space 和 To Space。每次垃圾回收时,活动对象会从 From Space 复制到 To Space。其工作原理如下:
- 分配阶段:内存分为两部分,每部分用于存储活动对象。活动对象存放在当前的 From Space 中。
- 回收阶段:当垃圾回收发生时,所有存活对象会被复制到 To Space,然后清空 From Space。
- 优势:没有内存碎片,因为每次回收都将存活对象移动到连续的内存区域。
- 问题:该算法的内存利用率只有 50%,因为每次只能使用其中的一部分内存,另一部分则用作备用。
优化方式:常用于年轻代的垃圾回收,GC时只需要对年轻代进行回收,分配较为高效。
# 3. 标记-整理算法(Mark-Compact)
标记-整理算法结合了标记-清除算法的标记阶段和复制算法的整理阶段。其工作原理如下:
- 标记阶段:与标记-清除算法相同,标记所有存活的对象。
- 整理阶段:不同于标记-清除算法,标记-整理算法会将所有存活对象移动到内存的一端,紧凑的排列在一起,然后清除剩余的空闲内存区域。
优势:解决了标记-清除算法的内存碎片问题,避免了内存被碎片化,从而更高效地利用内存。
问题:整理阶段需要对对象进行移动,这会带来额外的开销。
# 4. 各算法的优缺点对比
标记-清除算法:
- 优点:简单、易于理解。
- 缺点:存在内存碎片问题,效率较低。
复制算法:
- 优点:解决了内存碎片问题,适合短生命周期的对象。
- 缺点:内存利用率较低(只有一半的内存可用),且回收时需要复制所有存活对象。
标记-整理算法:
- 优点:避免了碎片化,内存利用率较高。
- 缺点:对象需要移动,增加了回收的时间开销。
# 5. 最佳实践
- 新生代使用复制算法:由于新生代的对象生命周期较短,复制算法可以高效地回收新生代对象,避免碎片化问题。
- 老年代使用标记-整理算法:老年代对象的生命周期较长,标记-整理算法适合于老年代的垃圾回收,避免内存碎片并提高内存利用率。
- GC 参数调优:通过调整 JVM 参数(如
-XX:NewRatio
、-Xms
、-Xmx
等)来优化不同区域的垃圾回收策略,以减少GC的影响。
# 深入追问
- 在不同的应用场景中,如何根据对象生命周期来选择不同的垃圾回收算法?
- 为什么常用的 JVM 使用 分代回收 机制,将复制算法与标记-整理算法结合使用?
- 如何分析 JVM 在进行标记-清除和标记-整理过程中可能带来的性能瓶颈?
# 相关面试题
- Java 中的垃圾回收算法是如何演化的?
- 什么是分代垃圾回收?如何优化新生代和老年代的垃圾回收?
- Java 中的垃圾回收器(如 G1、CMS、ParallelGC)是如何选择合适的算法来进行内存回收的?