鉴于复制算法中,没有内存碎片的方式能大大提高分配效率,因此,在mark_sweep的基础上,也可以改良出mark_compact算法,使得空闲空间连续。同时又没有复制算法的无帮吃一半堆的问题。
首先是最简单的算法,叫Lisp2算法。
在标记阶段,不变,仍然是对每个活动对象打上标签。
随后,开始移动活动对象到堆的开头。因为没有两个堆,又要挪动对象放到一起,那么forwarding指针便需要在对象头中占用空间(复制算法中不用占用是因为它是非活动对象),且在移动前要做一轮forwarding指针的重指。还要做一轮子对象指针的重指。总体流程如下:
compaction_phase() { set_forwarding_ptr() adjust_ptr() move_obj() } // 设forward指针最简单set_forwarding_ptr() { scan = new_addr = $heap_start while (scan < $heap_end) if (scan.mark == TRUE) scan.forwarding = new_addr new_addr += scan.size scan += scan.size } // 调整对象的成员指针,指向成员在堆中的新地址。对于全局变量,因为它们是根的子对象,也需要重指。adjust_ptr() { for (r : $roots) r = r.forwarding scan = $heap_start while (scan < $heap_end) // 这里为什么要扫堆,而不能从根直接递归遍历?因为直接递归可能会递归到不正确的已经重指过的值。直接遍历的话不会有顺序问题。 if (scan.mark == TRUE) for (child : children(scan)) child = child.forwarding scan += scan.size } // 最后,移动对象//不列了,比较简单,只需要扫堆,把活动对象向前移,清除forwarding和mark标记即可