目錄算法
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速度也會提高。
缺點:花費時間檢測黑名單。