由标记阶段和清除阶段组成。标记是把所有活动对象做上标记,清除是把哪些没有标记(活动)的对象回收的阶段。
标记阶段
mark(obj){
if(obj.mark == FALSE)
obj.mark = TRUE
for(child : children(obj))
mark(*child)
}
mark_phase(){
for(r : $roots)
mark(*r)
}
标记阶段从root开始递归地给堆里所有对象打上标记。标记算法一般是用深度或者广度搜索,深度搜索可以压缩内存使用量,所以一般用深度
清除阶段
collector会遍历整个堆,回收没有被标记的对象,将要回收的block插入free_list 链表,还在使用的对象则取消标志位
sweep_phase(){
sweeping = $heap_start
while(sweeping < $heap_end)
if(sweeping.mark == TRUE)
sweeping.mark = FALSE
else
//将需要回收的block头插入到free_list 链表,再跳到下一个block
sweeping.next = $free_list
$free_list = sweeping
sweeping += sweeping.size
}
分配
当mutator想要申请分块时,直接搜索free_list 链表寻找大小合适的块
new_obj(size){
chunk = pickup_chunk(size, $free_list)
if(chunk != NULL)
return chunk
else
allocation_fail()
}
pickup_chunk()遍历链表找到合适的block,如果找到比size大的块,会涉及到一个切割的操作(久了会导致内存碎片化的问题)
分配策略
- First-fit : 第一次找到合适的就返回
- Best-fit : 遍历完返回大小等于size的最小分块
- Worst-fit : 找到空闲链表中最大的分块切割需要的size后返回(不推荐,很容易内存碎片化)
合并
主要是解决一些连续内存碎片化的问题,当大量的小分块是连续的时候,可以把他们拼接在一起形成一个大分块,这个操作在清除阶段执行
执行合并的清除代码:
sweep_phase(){
sweeping = $heap_start
while(sweeping < $heap_end)
if(sweeping.mark == TRUE)
sweeping.mark = FALSE
else
//合并start
if(sweeping == $free_list + $free_list.size)
$free_list.size += sweeping.size
//合并end
else
sweeping.next = $free_list
$free_list = sweeping
sweeping += sweeping.size
}
优点
- 实现容易
- 与保守式GC算法兼容
缺点
- 碎片化问题
- 分配速度较慢:此算法分块时不连续的每次分配都要遍历空闲链表
- 与写时复制技术不兼容(可用bitmap marking方法解决)
优化:多个空闲链表
每个空闲链表负责一定范围大小的block
new_obj():
new_obj(size){
index = size / (WORD_LENGTH / BYTE_LENGTH)
if(index <= 100)
if($free_list[index] != NULL)
chunk = $free_list[index]
$free_list[index] = $free_list[index].next
return chunk
else
chunk = pickup_chunk(size, $free_list[101])
if(chunk != NULL)
return chunk
allocation_fail()
}
清除函数sweep_phase():
sweep_phase(){
//清除空闲链表指针数组里面存的指针索引
for(i : 2..101)
$free_list[i] = NULL
//遍历清除标记,空闲block存入对应空闲链表
sweeping = $heap_start
while(sweeping < $heap_end)
if(sweeping.mark == TRUE)
sweeping.mark = FALSE
else
index = size / (WORD_LENGTH / BYTE_LENGTH )
if(index <= 100)
sweeping.next = $free_list[index]
$free_list[index] = sweeping
else
sweeping.next = $free_list[101]
$free_list[101] = sweeping
sweeping += sweeping.size
}
BiBOP方法
Big Bag Of Pages ,目的是将大小相近的对象整理成固定大小的block进行管理的方法。就是把堆分割成固定大小的块,每一块只能配置同样大小的对象
此方法可以提高内存的使用效率,不会出现大小不均的分块,但有时候可能会降低堆的使用效率,比如此图中大小为2的block中的内存大部分都没有使用.
bit marking 方法
常规的标记-清除算法无法兼容写时复制技术,因为算法是把对象头和消息实体一并清除的。
将对象的头和实体信息分离,头信息存储在单独的bitmap table中,不和对象实体一起管理,常用的bitmap table数据结构是散列表和树
位图表格中位的位置要和堆里的各个对象切实对应,一般堆中的一个字会分配。
bit-marking的标记函数mark():
mark(obj){
obj_num = (obj - $heap_start) / WORD_LENGTH
index = obj_num / WORD_LENGTH
offset = obj_num % WORD_LENGTH
if(($bitmap_tbl[index] & (1 << offset)) == 0)
$bitmap_tbl[index] |= (1 << offset)
for(child : children(obj))
mark(*child)
}
- WORD_LENGTH指当前机器的一个字的位宽(32位机器就是32,64位机器就是64)
- obj_num 指obj的的排序号,表明当前对象从heap_start算起的话排第几
- index 和offset 是排序号对应的存储二维数组的横坐标和纵坐标
- 然后判断当前位置的bit位是否已经被mark了(置为1),如果没有则写为1,并递归地mark()
优点
- 与写时复制技术兼容,bit-marking方法不会直接重写对象本身,不会发生无畏的复制
- 清除操作效率高,不用遍历整个heap,直接清理bit-table就行了
//把当前空闲的block添加到free_list下,再把bit-table全部清零
sweep_phase(){
sweeping = $heap_start
index = 0
offset = 0
while(sweeping < $heap_end)
if($bitmap_tbl[index] & (1 << offset) == 0)
sweeping.next = $free_list
$free_list = sweeping
index += (offset + sweeping.size) / WORD_LENGTH
offset = (offset + sweeping.size) % WORD_LENGTH
sweeping += sweeping.size
for(i : 0..(HEAP_SIZE / WORD_LENGTH - 1))
$bitmap_tbl[i] = 0
}
注意下如果有多个heap,则每个heap需要一个bit-table
延迟清除法
堆越大清除操作需要的时间越多,延迟清除就是在标记结束后不一定清除,而是延时
mark()时先进行清除,如果清除的块满足申请要求,则直接返回当前清除的块,否则再做mark,再用lazy_sweep()来分配分块
new_obj(size){
chunk = lazy_sweep(size)
if(chunk != NULL)
mark_phase()
chunk = lazy_sweep(size)
if(chunk != NULL)
return chunk
allocation_fail()
}
lazy_sweep()函数, $sweeping 在这里是全局变量:
lazy_sweep(size){
while($sweeping < $heap_end)
if($sweeping.mark == TRUE)
$sweeping.mark = FALSE
else if($sweeping.size >= size)
chunk = $sweeping
$sweeping += $sweeping.size
return chunk
$sweeping += $sweeping.size
$sweeping = $heap_start
return NULL
}
延迟清除法在清除标记的时候找到一个合适大小的块就会返回给申请者,不是一下子遍历整个heap。
此方法的问题是效果不均衡,heap中垃圾分布不均的时候容易出问题: