Serial Old是Serial收集器的老年代版本,也属于单线程串行收集器,采用的算法为Mark-Compact(标记-整理算法)。
与用于新生代的Serial收集器“复制算法”不同。标记-整理算法可以简单认为是标记-清除算法和复制算法的组合。
图2-1:标记-整理算法示意图
标记-整理算法在标记阶段与标记-清除算法类似,但是其“整理”阶段较为复杂。首先,标记可到达的对象,然后压缩步骤将可到达(标记的)对象重新定位到堆区域的开头。在以与标记-清除算法相同的方式标记堆中的活动对象之后,堆通常会被碎片化。标记-整理算法的目标是将内存中的活动对象移到一起,从而消除碎片。面临的挑战是正确更新指向已移动对象的所有指针,其中的大多数对象在压缩后将具有新的内存地址。处理指针更新的问题以不同的方式处理。
理论基础:基于表的压缩算法(Table-based compaction)
Haddon和Waite在1967年首先描述了基于表的压缩算法。它保留了活动对象在堆中的相对位置,并且仅需要恒定量的开销。
图2-2:表堆压缩算法。标记阶段已确定可到达(活动)的对象为彩色,可用空间为空白。
压缩从堆的底部(低地址)到顶部(高地址)进行。遇到活动(即已标记)对象时,它们将移至第一个可用的低位地址,并且一条记录会附加到重定位信息的中断表中。对于每个活动对象,中断表中的记录包括压缩之前对象的原始地址以及压缩之后原始地址与新地址之间的差。中断表存储在正在压缩的堆中,但存储在标记为未使用的区域中。为了确保压缩将始终成功,堆中的最小对象大小必须大于或等于中断表记录的大小。
最终jvm实现的算法为LISP2,为了避免O(n log n)复杂性,LISP2算法在堆上使用了3次不同的遍历。另外,堆对象必须有一个单独的转发指针槽,该槽在垃圾回收之外不使用。经过标准标记后,该算法将进行以下3遍:
1.计算活动对象的转发位置。
跟踪可用指针和活动指针,并将它们都初始化为堆的开始。
如果活动指针指向活动对象,则将该对象的转发指针更新为当前的空闲指针,并根据对象的大小增加空闲指针。
将活动指针移动到下一个对象
当活动指针到达堆末尾时结束。
2.更新所有指针
对于每个活动对象,请根据它们所指向的对象的转发指针来更新其指针。
3.移动物件
对于每个活动对象,将其数据移动到其转发位置。
该算法在堆大小上为O(n)。它比基于表的方法具有更好的复杂性,但是基于表的方法的n仅是已使用空间的大小,而不是LISP2算法中的整个堆空间。但是,LISP2算法更易于实现。
随着java的发展,标记-整理算法的整理部分的实现已经从3次遍历优化为2次遍历。