垃圾收集算法

垃圾回收算法

 

一、标记——清除算法

二、复制算法

三、标记——整理算法

四、分代收集算法


一、标记——清除算法

     标记——清除算法是最基础的收集算法。算法过程分为“标记”和“清除”两个阶段,。首先通过“引用计数”或者“可达性分析”标记出所有需要回收的对象,在标记完成之后统一回收所有被标记的对象。它的主要不足有两个:一个是效率问题,标记和清除两个过程的效率都不高;另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。标记—清除算法的执行过程如下所示:

垃圾收集算法

二、复制算法

     复制算法实际上是为了解决标记—清除算法中空间问题的一个改进。复制算法整体思想将可用容量整体划分为两个区域,每次只使用其中的一块区域,而另一块区域就成为了“存活对象”的一个“备胎区”,当使用的那一块内存区域用完时,即将存活的对象复制到“备胎区域”,然后再把使用的这一块内存区域全部清理,这样使得每次回收都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可。这种算法实现简单,运行效率较高,但它牺牲了一半的内存,实际上付出的代价是高昂的。在现在的商业虚拟机中将这种算法应用于活动较为频繁的新生代。新生代的对象98%都是“朝生夕死”,所以在复制算法原有的基础上做了改进,取消了原本按1:1划分内存空间的方式,它将内存划分为Eden区、survivor区(2个)和old区。每次使用Eden和1块Survivor区域,当启动回收时,将存活的对象复制到未使用的Suvivor空间,并清理Eden和使用过的Survivor空间。在HotSpot虚拟机中默认Eden和Survivor区的大小比例为8:1,也就是每次新生代可用内存空间为新生代的90%,只有10%的内存会被浪费。这样的内存划分方式虽然很大程度上提高了内存空间的一个利用率,但当存活的对象所需内存超过了10%或者更多时,不仅由于要进行大量的复制操作导致效率降低,而且Survivor区域存储不下这么多对象时,就需要依赖老年代内存进行分配担保,将部分对象移至老年代。

垃圾收集算法

三、标记——整理算法

     复制收集算法在存活率较高时就要进行较多的复制操作,效率将会变低。更关键是,存活对象较多时,需要有额外的空间进行分配担保,所以在老年代一般不采用复制算法。老年代采用了“标记——整理”算法,标记过程和“标记——清除”算法一样,只是后续不是直接对可回收的对象进行清理,而是让所有存活的对象先向一端移动,然后直接清理掉端边界以外的内存,相对“标记——清除”算法,它避免了产生空间碎片问题,相对“复制算法”,它避免了大量对象存活时的一个复制操作量较高导致的效率低下的问题。

垃圾收集算法

四、分代收集算法

分代收集算法可以说是“复制算法”+“标记——整理算法”。JVM根据对象存活周期将内存区域划分为老年代和新生代。对于“朝生夕死”的新生代采用了“复制收集算法”,对于对象存活率较高的老年代采用“标记——整理算法”。