Skip to content

TIP

主流的垃圾收集算法主要有3种:标记-清除算法、复制算法、标记-整理算法。

垃圾算法的作用:当成功区分出内存中存活对象和死亡对象后,GC接下来的任务就是执行垃圾回收,释放掉无用对象所占用的内存空间,以便有足够的可用内存空间为新对象分配内存。

标记-清除算法

标记-清除算法是世界上第一个GC算法,它分为两个阶段:

  • 标记阶段:标记所有需要回收的对象。核心原理是递归地遍历(从GCRoot开始)所有可达对象,通过标记,算法能够确定哪些对象是可达的,而不会被回收。
  • 清除阶段:同一回收所有被标记的对象。核心原理是算法遍历整个堆,回收未标记的对象,并将它们的内存释放回系统

标记-清除算法内存布局

此算法有两个缺点:

  1. 效率问题:标记和清除两个过程的效率都不高(标记时间与堆对象的总数成正比);
  2. 空间问题:标记清除之后会产生大量不连续的内存空间碎片,如上图空白部分;空间碎片太多可能会导致以后在程序的运行过程中需要分配较大的对象时,会因为无法找到足够大的连续内存空间而不得不提前触发另一次垃圾收集行为。

复制算法

还记得这张图吗,Eden:From:To = 8:1:1,那么为什么要这么设计呢?

堆内存分配

如下图所示,如果是标记-清除算法在进行GC时,C部分会空余出来,但是会造成内存碎片。

复制算法1.drawio

将A对象复制到to空间

为了解决内存碎片问题,引入From和To空间来解决这个问题,具体是怎么解决的呢?来看步骤一,将from空间的A对象复制到to空间。由于A有引用BD对象,所以to空间的A也必定引用到BD对象。由于BD还在from空间,故to空间的A任然持有from空间的BD引用。

复制算法2.drawio

被引用拷贝到To空间

把A引用的BD拷贝到To空间,同时将引用关系重新标记,将A设置为GCRoot

复制算法3.drawio

清除From空间

复制算法4.drawio

转换From空间与To空间

为什么要转换?因为Eden只向From空间传递数据,完成GC后要将数据转移会From空间。

复制算法5.drawio

TIP

所以如果问年轻代为什么要设计两个Survivor(form,to)空间,本质上就是提问复制算法。

优缺点总结

  • 优点:没有碎片化;吞吐量高(GC复制算法只搜索并复制活动对象,消耗时间与活动对象数量成正比,与堆大小无关)。
  • 缺点:空间浪费,会浪费一半内存。

标记-整理算法

标记-整理算法结合了“标记-清除"和“复制”两个算法的优点,也是分两阶段;第一阶段和标记清除算法一样,从根节点开始标记所有被引用对象。第二阶段将所有的存活对象压缩到内存的一端,按顺序排放;最后,清理边界外所有的空间。

标记-压缩算法的最终效果等同于标记-清除算法执行完成后,再进行一次内存碎片整理,因此也可以把它称为标记-清除-压缩算法。

标记-整理算法.drawio

优缺点总结

  • 优点:标记-整理算法不仅弥补标记-清除算法内存区域分散导致内存碎片化的问题,也解决了复制算法中,内存浪费一半的问题。
  • 缺点:效率低,要标记所有存活对象,并且拷贝到一个新的地方,还得更新它们的引用地址;从效率上来说,标记/整理算法要低于复制算法。