# 28. 常见GC算法有哪些?标记-清除、复制、标记-整理有何区别?

# 标准答案

Java 中的常见垃圾回收算法有 标记-清除算法复制算法标记-整理算法,它们分别以不同的方式回收不再使用的对象。

  • 标记-清除算法:标记所有需要回收的对象,然后清除它们,存在碎片问题。
  • 复制算法:将内存分为两个区域,使用一个区域存储活动对象,回收时将活动对象复制到另一区域,减少碎片。
  • 标记-整理算法:标记需要回收的对象,接着将存活对象整理到一端,清除未使用的内存,避免碎片问题。

# 答案解析

# 1. 标记-清除算法(Mark-and-Sweep)

标记-清除算法是最基础的垃圾回收算法,其工作原理如下:

  • 标记阶段:遍历所有对象,标记那些可达的对象(即存活的对象)。这通常通过可达性分析来完成,从GC Root出发,沿着引用链遍历标记可达的对象。
  • 清除阶段:清除所有未标记的对象,即不可达的对象。

问题:标记-清除算法的一个明显问题是 碎片化,因为回收过程中不会移动对象,只是标记和清除对象,这会导致内存中存留大量空闲空间(即内存碎片)。随着时间的推移,这会影响内存的使用效率,甚至导致无法分配足够的内存。

# 2. 复制算法(Copying)

复制算法通过将内存分为两部分,分别为 From SpaceTo 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)是如何选择合适的算法来进行内存回收的?