标记清除 (Mark-Sweep)

顾名思义,标记清除算法分为两步:标记 => 清除。首先系统标记那些对象是可以回收的,上面的文章中笔者已经详细的讨论了二次标记的过程,这里不再赘述。 标记清除算法是最基本的算法,因为后续的两种算都是基于标记清除优化实现的。其主要的不足点就是: 效率不高以及产生大量连续的内存碎片。

其算法过程如下:
image.png

  • 效率问题: 标记算法和清除算法都不是一种效率高的算法,需要扫描所有的堆空间,造成效率低
  • 空间问题: 标记清除后会产生大量不连续的内存空间,导致出现后续使用的过程中出现无法使用的问题,从而再次出发GC,GC次数越多导致更严重的碎片化问题

复制算法(Coping)

此方法适用于新生代,新生代存活的变量很少,基本上大量的对象会被回收掉

标记清除算法的效率不高,基于标记清除算法演化的复制算法,将整个内存区域划分为From区域和To区域,每次均使用From区域的空间,To区域闲置,当From区域的内存出现无法不足的时候,将会将From区域中存活的对象复制到To区域中,然后清空From区域,这时候交换From和To区域(其本质是移动内存指针)即可完成内存清除。

image.png

上面的演示图中,将内存区域划分为1:1 的模块,实际的商业化虚拟机比如HtoSptVM,将新生代内存区域划分为一个Eden区域以及2个Survivor区域,其中Eden占比80%,Survivor占比10%,每次使用 一份 Eden和Survivor区域,GC的时候将两者存活的对象复制到另外一个Survivor区域,然后清理掉原Eden和Survivor空间,完成复制算法的过程。这样的话,每个也就10%的区域被浪费。
也就是说每次GC的时候,最多只能有10%的空间大小的对象存活,这显然不能被保证的的,所以在存活对象大小多出10%的内存区域后,会依赖于老年代内存区域,将部分对象分配至老年代,进行分配担保。

  • 只需要扫描存活的对象,效果更高
  • 不会产生内存碎片
  • 适合生命周期比较短的对象,比如新生代的对象,因为大量的对象被回收,使用复制算法效率更高

据IBM 的研究发现,98% 的对象,只能存活一个GC 周期,这些对象适合使用复制算法,而且From 和 To 区域也不用 1:1 划分

标记整理(Mark-Compact)

复制手机算法,在存活率比较高的内存区域中,需要进行大量的复制操作,显然效率不高,因此复制算法仅仅适用于对象存活率不高的内存区域,比如朝生夕死的新生代内存区域,对于存活率很高的老年代内存,亦然使用复制算法显然不合适。
根据老年代的特点,提出了一种’标记-整理’ ,同标记-清理一样,首先标记哪些对象可以进行回收,后续的部分不是清理,而是将存活的对象向一段移动,然后直接清理掉边界另一侧的内存,其流程如下图所示:
image.png

  • 没有内存碎片
  • 现在主要的工作在对象的移动或者压缩

分代回收

当前主流的VM采用的均是分代收集的算法,根据对象不同的存活周期将内存划分为不同的区域,一般划分为新生代和老年代。这样根据不同年代的特点来采用合适的手机算法。

  • 新生代内存每次GC都有大量的对象失去,只有少量的对象存活,因此采用复制算法;
  • 老年代存活率高,没有额外的空间进行分配担保,因此采用标记-整理算法。