GC标记-清除算法

由标记阶段和清除阶段组成。标记是把所有活动对象做上标记,清除是把哪些没有标记(活动)的对象回收的阶段。


标记阶段

这里写图片描述

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中垃圾分布不均的时候容易出问题:
这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值