常见的【垃圾收集算法】
目录
- 1. 标记 - 清除算法(Mark-Sweep)
- 2. 复制算法(Copying)
- 3. 标记 - 整理算法(Mark-Compact)
1. 标记 - 清除算法(Mark-Sweep)
自己的理解:
标记清除算法:从GC root出发扫描,并标记所有存活的对象,之后清除没有标记的对象。
优点是:核心流程简单;
缺点是:导致内存碎片化严重(无法分配需要连续空间的大对象,例如数组)。在可回收的对象(垃圾对象)多时,大量的标记清除特别耗时(由于标记过程需要全堆遍历,会使得 GC 停顿时间较长,比较耗时)
注意:标记用的是三色标记法
更专业一点:
标记 - 清除算法是通过 GC Roots 扫描标记存活对象,回收未标记对象的垃圾回收算法。
其核心流程简洁,但存在明显缺陷:标记阶段全堆遍历,对象数量多时常导致 GC 停顿久;回收后产生的内存碎片化,会引发大对象分配失败、后续内存分配和垃圾回收效率降低等问题。
该算法常应用于对停顿时间要求不是极端严格,且能通过其他机制一定程度缓解碎片化影响的场景(如老年代部分垃圾回收场景),同时也因这些特点,在与复制算法、标记 - 整理算法的对比中,有独特的适用范围和权衡考量。
-
流程:
- 标记:遍历对象图,标记所有存活对象。
- 清除:遍历堆内存,回收未标记的对象(直接释放内存)。
-
优缺点:
-
优点:实现简单,无需移动对象。
-
缺点:产生内存碎片(零散的空闲内存无法分配大对象(数组));标记和清除过程耗时(全堆遍历)。
-
2. 复制算法(Copying)
自己的理解:
标记-复制算法:将内存分为两个大小相同的区域,每次只使用其中一个,当垃圾回收时将存活的对象复制到另一块区域,然后清除正在使用的区域。
优点是无内存碎片。
缺点是:首先当一个区域存活的对象太多时,需要较多的内存间复制,效率低。
第二个是浪费内存,每次只使用一半的内存。特别适合新生代,因为新生代每次存活的对象少,复制就少,性能影响就不那莫明显。
补充:
HotSpot 里新生代用 8:1:1 的 Eden+Survivor 结构,本质也是这种算法的优化,既保留无碎片优势,又提高了内存利用率
- 流程:
将内存分为两块等大区域(如新生代的 Eden + s0、s1),每次只用其中一块。回收时,将存活对象复制到另一块空白区域,然后清空当前区域。 - 优缺点:
- 优点:无内存碎片,适合存活率低的区域(如新生代)。
- 缺点:浪费内存(使现有得可用空间变为原来得一半);复制对象有性能开销(小对象影响小,大对象成本高)。
3. 标记 - 整理算法(Mark-Compact)
自己的理解:
标记-整理算法:标记过程和标记清除过程一样,首先是从GC Roots出发标记存活的对象,但后续过程它是将存活的对象移到内存一端,然后直接清除边界以外的内存。
优点是解决了内存碎片的问题,缺点是需要移动对象,可能有性能开销。
更全面一点:
“标记 - 整理算法的标记阶段和标记 - 清除一致,都是从 GC Roots 出发标记存活对象。但后续它会把存活对象移动到内存的一端,然后清除边界外的所有内存。
优点是彻底解决了内存碎片化问题,避免大对象分配失败;缺点是移动对象时需要修改引用地址,可能带来性能开销,尤其是存活对象多的时候。
这种算法很适合老年代,因为老年代对象存活率高,用复制算法成本太高,用标记 - 清除会有碎片,而标记 - 整理能平衡这两点,虽然有移动成本,但老年代回收频率低,整体更合理。”