垃圾收集的算法的实现是很复杂的,我这讲的只是介绍下几种算法的思想。
标记-清楚算法
简单来说就是先标记,在把标记的对象回收。
这种存在两个问题:
- 效率很低,标记和清除都是需要遍历一遍。
- 会产生内存碎片,在分配大对象的时候无法找到连续的内存来存放,从而不得不进行一次垃圾回收。
复制算法
针对标记-清楚算法的效率问题,复制算法出现了。
复制算法,把内存分成两个相同的块,每次只使用其中的一块。当这块被用完了那么就将存活的对象复制到另外一块上面,然后把使用过的那块内存空间一次清除掉。
这种做法只需要对半个区进行移动,只需要移动堆顶的指针。实现简单,运行起来高效。(相对于标记-清楚少做了一遍遍历)。但缺点也很明显,可以使用的内存相当于是以前的一半。
虽然这种方法会浪费内存,但在现在的商业虚拟机都采用了这种手机算法来回收新生代。这是因为在新生代中的对象98%都是“朝生夕死”的,所以并不需要上面说的按照1:1的比例来分配,而是有一块较大的Eden区和2个较小的Survivor区。新生成的对象会在Eden区和其中一块Survivor区,另一块Survivor区是用来把垃圾回收后中Eden和Survivor区还存活的对象。HotSpot虚拟机默认是分配Eden区和Survivor区的比例是90:1。这么说只有10%的内存空间被浪费。但这其中会有一个问题,就是在Surivior区无法放下存活的对象了,这个时候就需要其他内存区域提供诉费担保。(诉费担保会在后面详细讲到)
标记-整理算法
复制算法虽然能在年轻带中使用,但是在老年代中对象的存活率较高。如果使用复制算法每次复制的对象都是非常多的,所以效率比较低,所以不能再老年代使用这种算法。
根据老年代的特点,有人提出了“标记-整理”算法,这种算法就是在标记-清除算法的基础,把所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。如下图所示:
小结:
JVM的垃圾回收算法只是根据对象存活周期的不同将内存划分为几块,一般是分为新生代和年老代。新生代中每次垃圾回收的时候都会有大批的对象死去,只有少部分是存活的,这时候就可以采用复制算法。年老代中对象存活率较高,而且没有额外空间对它进行分配,这时候就可以采用标记-整理算法。