目录算法
Mostly Copying GC, Joel F.Bartlett, 1989数据结构
此算法能够在不明确根的环境中运行GC复制算法。函数
Mostly Copying GC就是“把不明确的根指向的对象之外的对象都进行复制”,抛开那些不能移动的对象将其余大部分的对象进行复制的GC算法。spa
下图所示,是 Mostly Copying GC的堆结构,堆被分红必定大小的页(page),每一个页都有编号。那些没有分配到对象的空页则有一个$current_space之外的编号。页编号不能形成数据溢出。3d
GC时,$current_space 和 $next_space用于识别To空间和From空间。编号和$next_space同样的是To,编号和$current_space同样的是From页面。通常状况下$current_space和$next_space是同一个值,只有在GC时才会不一样。$current_space的值会被分配到装有对象的正在使用的页。如上图示,正在使用的页编号为1.指针
此外,咱们要为正在使用的页设置一下两种标志的一种。code
CONTINUED :当正在使用的页跨页时,设置在第二个页以后。对象
以上两个标识不是一块排列在内存当中,由于会出现跨页分配对象,因此从实现上来讲,咱们必须把页和标识分配在不一样的内存位置进行管理。blog
根据分块的大小、分配对象的大小不一样,分配的动做也各不相同。内存
分配的伪代码
new_obj(size){ while(size > $free_size) // $free_size用来保持分块大小 $free_size = 0 add_page(byte_to_page_num(size)) obj = $free obj.size = size if(size < PAGE_SIZE) $free_size -= size $free += size else $free_size = 0 return obj }
负责从新分页的add_pages()函数。
add_pages(page_num){ if($allocated_page_num + page_num >= HEAP_PAGE_NUM/2) mostly_copying() return first_free_page = find_free_pages(page_num) if(first_free_page == NULL) allocation_fail() if($next_space != $current_space) enqueue(first_free_page, $to_space_queue) allocate_page(first_free_page, page_num) }
allocate_pages(first_free_page, page_num){ $free_page = first_free_page $free = first_free_page $free_size = page_num*PAGE_SIZE $allocated_page_num += page_num set_space_type(first_free_page, $next_space) set_allocate_type(first_free_page, OBJECT) while(--page_num > 0) $free_page = next_page($free_page) set_space_type($free_page, $next_space) set_allocate_type($free_page, CONTINUED) $free_page = next_page($free_page) }
下图标识GC执行前堆的状态。这时$current_space和$next_space的值是相同的。首先对$next_space进行增量。一旦GC开始执行,与$current_space值相同的页就是From页,与$next_space值相同的页就是To页。
以后咱们将那些保留有从根引用的对象的页“晋升promotion”到To页。下图示:这里的晋升是指将页的编号设定为$next_space的值把它当作To空间处理。
由于对象A是根引用的,因此咱们将该对象的页面编号设定为$next_space的值。也就是$next_space = 2
把全部从根引用的页都晋升后,下面就是把To页里的对象的子对象复制到空页了。这个时候对象Y(垃圾对象)引用的D也会被复制过去。而后空页的编号会被设定为$next_space。也就是说这个页变为了To页。
接下来,咱们要把追加的To页里的对象的子对象复制到To页的分块里。若是To页里没有分块,那么对象就会被复制到空页,目标页的编号会被设定为$next_space。上图中To页有分块,因此直接复制对象E。以下图示:
当全部对象的子对象复制完毕后GC就结束了,此时$current_space的值设定为$next_space的值。以下图示:
从上图得知,垃圾对象X,Y,D都没有被回收。MostlyCopyingGC的特殊之处就是不会回收包含有从根指向的对象(A)所在页的垃圾对象,而且也不会回收这个垃圾对象所引用的对象群。极端一点,若是全部页里都有对象被根指着,表明全部垃圾不能被回收。
缺页能够经过调整也大小来改善。实验代表页大小适合在512字节。实际上本身在生产环境中那个好就是那个了。
该方法是用来执行GC的函数,由add_pages()调用。
mostly_copying(){ $free_size = 0 //为了避免把对象复制到From空间里去,GC将From页里的分块大小设置为0 $allocated_page_num = 0 $next_space = ($current_space) %N // 将next_space进行增量。为了不$next_space溢出,增量时必取常量N余数。 for(r :$roots) //保留根直接引用的对象所在页。 promote_page(obj_to_page(*r)) //obj_to_page函数将对象做为参数,返回保留的对的页。 while(is_empty($to_space_queue) == FALSE) //复制To页里的子对象。除去CONTINUED页,全部的To页都链接到了$to_space_queue。咱们将其取出并传递给page_scan(). page_scan(dequeue($to_space_queue)) $current_space = $next_space }
MostlyCopyingGC不会特地把因GC变空的空页的编号置为0.所以空页的编号可能会很混乱,为此常量N的数值必须必空页的总数大得多,以保证及时给全部空页分配惟一的编号,程序也能识别编号被设为$next_space的页和其余的页。
是将用做参数的页晋升的函数。若是用做参数的页 里的对象跨了多个页,那么这些页都会被一块儿晋升。
promote_page(page){ if(is_page_to_heap(page) == True && space_type(page) == $current_space && allocate_type(page)== OBJECT) promote_continued_page(next_page(page)) //下面有源码 // 将晋级的page链接到$to_space_queue set_space_type(page, $next_space) $allocated_page_num++ enqueue(page, $to_space_queue) }
promote_continued_page(page){ while(space_type(page) == $current_space && allocate_type(page) == CONTINUED) //调查用做参数的页编号是否为$current_space,以及是否设置了 CONTINUED 标志。 set_space_type(page, $next_space) $allocated_page_num++ page = next_page(page) }
对象不被分配到CONTINUED页,其缘由就是这里的最后一行代码。若是分配到了CONTINUED页,那么对象就有可能跨页,此时CONTINUED页的下一个页会颇有可能也是CONTINUED。若是从新放置到一个空页的话,它是没有下一页的。这就形成了本来不用也不想复制的对象因为在CONTINUED中因此也被复制了。
把那些持有从根引用的对象的页所有晋升后,下面就要复制到To页里的对象的子对象。
page_scan()函数,是经过mostly_copying()函数调用的函数。这个函数只接受To页做为参数。
page_scan(to_page){ for(obj : objects_in_page(to_page)) for(child : children(obj)) *child = copy(*child) }
这个函数被用于将页里全部对象的子对象都交给 copy() 函数,并把对象内的指针都改 写成目标空间的地址。
将复制对象用做参数。
copy(obj){ if(space_type(obj_to_page(obj)) == $next_space) //检查持有obj的页是不是To页。若是是就不会被复制直接返回对象。 return obj if(obj.field1 != COPIED) //检查对象是否复制完毕。 to = new_obj(obj.size) // 没有复制完毕,则使用该方法来分配空间 copy_data(to, obj, obj.size) //将对象复制。 obj.field1 = COPIED // 修改复制标记,表示已复制。 obj.field2 = to // 更改指针地址 return obj.field2 //返回对象地址(也就是目标空间的地址即原对象forwarding) }
优势:使用了GC复制算法,包含它的优势。
缺点:部分垃圾没有被回收。
Hans J.Boehm 黑名单法
保守式GC的缺点之一,就是使用指针识别错误,原本要被删除的垃圾却被保留了下来,甚至形成其余更严重的错误。改善这个问题可采用Hans J.Boehm 发明的黑名单法。
在指针的错误识别中,被错误判断为活动对象的那些垃圾对象的大小及内容相当重要。
这个黑名单里记录 的是“不明确的根内的非指针,其指向的是有可能被分配对象的地址”。咱们将这项记录操做称为“记入黑名单”。 可能被分配的对象的地址指的是堆内未使用的对象的地址。
mutator没法引用至今未使用过的对象若是,根里存在有这种地址的指针,那它确定就是“非指针”,就会被记入黑名单中。
们在GC标记-清除算法中的 mark() 函数里导入记入黑名单的操做,其伪代码以下。
mark(obj){ if($heap_start <= obj && obj <= $heap_end) if(!is_used_object(obj)) obj.next = $blacklist $blacklist = obj else if(obj.mark == FALSE) obj.mark == TRUE for(child :children(obj)) mark(*child) }
若是对象正在使用,is_used_obj()就会返回真。在GC开始时候黑名单会被丢弃,也就是说,在标记阶段须要注意的地址会被记录在新的黑名单里。
黑名单里记录的是“须要注意的地址”也就是说这个对象就极可能被非指针值所引用。在将对象分配到须要注意的地址时,为所分配的对象设以下限制条件。
优势:保守式 GC 因错误识别指针而压迫堆的问题获得缓解,堆使用效率提高,没有多余对象GC速度也会提高。
缺点:花费时间检测黑名单。