GC 複製算法(Copying GC)是 Marvin L. Minsky 在 1963 年研究出來的算法。說得簡單點,就是隻把某個空間裏的活動對象複製到其餘空間,把原空間裏的全部對象都回收掉。這是一個至關大膽的算法。在此,咱們將複製活動對象的原空間稱爲 From 空間,將粘貼活動對象的新空間稱爲 To 空間。
本文實現的是 Robert R. Fenichel 與 Jerome C. Yochelson 研究出來的 GC 複製算法,使用C語言實現git
對象在GC的世界裏,表明的是數據集合,是垃圾回收的基本單位。github
能夠理解爲就是C語言中的指針(又或許是handle),GC是根據指針來搜索對象的。算法
這個詞有些地方翻譯爲賦值器,但仍是比較奇怪,不如不翻譯……segmentfault
mutator 是 Edsger Dijkstra 琢磨出來的詞,有「改變某物」的意思。說到要改變什麼,那就是 GC 對象間的引用關係。不過光這麼說可能你們仍是不能理解,其實用一句話歸納的話,它的實體就是「應用程序」。
mutatar的工做有如下兩種:數據結構
mutator 在進行這些操做時,會同時爲應用程序的用戶進行一些處理(數值計算、瀏覽網頁、編輯文章等)。隨着這些處理的逐步推動,對象間的引用關係也會「改變」。伴隨這些變化會產生垃圾,而負責回收這些垃圾的機制就是 GC。
GC ROOTS就是引用的起始點,好比棧,全局變量spa
堆就是進程中的一段動態內存,在GC的世界裏,通常會先申請一大段堆內存,而後mutatar在這一大段內存中進行分配翻譯
活動對象就是能經過mutatar(GC ROOTS)引用的對象,反之訪問不到的就是非活動對象。設計
在複製算法中,使用順序內存分配(sequential allocation)策略,順序分配流程以下圖所示3d
維護一個free pointer,每次分配內存後移動該指針,limit-free的就是當前堆中可用內存的大小指針
首先是對象類型的結構:
爲了動態訪問「對象」的屬性,此處使用屬性偏移量來記錄屬性的位置,而後經過指針的計算得到屬性
typedef struct class_descriptor { char *name;//類名稱 int size;//類大小,即對應sizeof(struct) int num_fields;//屬性數量 int *field_offsets;//類中的屬性偏移,即全部屬性在struct中的偏移量 } class_descriptor;
而後是對象的結構,雖然C語言中沒有繼承的概念,可是能夠經過共同屬性的struct來實現:
typedef struct _object { class_descriptor *class;//對象對應的類型 byte forwarded;//對象已經移動的標記,防止被重複複製 object *forwarding;//目標位置 } object; //繼承 //"繼承對象"需和父對象object基本屬性保持一致,在基本屬性以後,能夠定義其餘的屬性 typedef struct emp { class_descriptor *class;//對象對應的類型 byte forwarded;//對象已經移動的標記 object *forwarding;//目標位置 int id; dept *dept; } emp;
有了基本的數據結構,下面就能夠進行算法的實現了
複製算法利用From空間進行分配。當From空間被徹底佔滿沒法分配時,GC會將活動對象所有複製到To空間。當複製完成後,會將From/To空間互換,爲下次GC作準備。在本算法中,爲了確保To空間能夠容納全部From空間的活動對象,須要From和To空間容量保持一致。
複製算法的流程以下圖所示:
複製算法中,須要將堆一分爲二,一半做爲from,一半做爲to
void gc_init(int size) { heap_size = resolve_heap_size(size); heap_half_size = heap_size / 2; heap = (void *) malloc(heap_size); from = heap; to = (void *) (heap_half_size + from); _rp = 0; }
新建立對象分配內存時,只須要移動free pointer便可
next_free_offset就是圖中的free pointer
object *gc_alloc(class_descriptor *class) { //檢查是否能夠分配 if (next_free_offset + class->size > heap_half_size) { printf("Allocation Failed. execute gc...\n"); gc(); if (next_free_offset + class->size > heap_half_size) { printf("Allocation Failed! OutOfMemory...\n"); abort(); } } int old_offset = next_free_offset; //分配後,free移動至下一個可分配位置 next_free_offset = next_free_offset + class->size; //分配 object *new_obj = (object *) (old_offset + heap); //初始化 new_obj->class = class; new_obj->forwarded = FALSE; new_obj->forwarding = NULL; for (int i = 0; i < new_obj->class->num_fields; ++i) { //*(data **)是一個dereference操做,拿到field的pointer //(void *)o是強轉爲void* pointer,void*進行加法運算的時候就不會按類型增長地址 *(object **) ((void *) new_obj + new_obj->class->field_offsets[i]) = NULL; } return new_obj; }
複製時,需從GC ROOTS開始遍歷對象圖,對每個存活的對象進行復制;複製後對象地址改變,還須要更新GC ROOTS引用的地址;
void copying() { next_forwarding_offset = 0; //遍歷GC ROOTS for (int i = 0; i < _rp; ++i) { object *forwarded = copy(_roots[i]); //先將GC ROOTS引用的對象更新到to空間的新對象 _roots[i] = forwarded; } //更新引用 adjust_ref(); //清空from,並交換from/to swap(&from,&to); }
複製算法流程以下:
copy方法:
object *copy(object *obj) { if (!obj) { return NULL; } //因爲一個對象可能會被多個對象引用,因此此處判斷,避免重複複製 if (!obj->forwarded) { //計算複製後的指針 object *forwarding = (object *) (next_forwarding_offset + to); //賦值 memcpy(forwarding, obj, obj->class->size); obj->forwarded = TRUE; //將複製後的指針,寫入原對象的forwarding pointer,爲最後更新引用作準備 obj->forwarding = forwarding; //複製後,移動to區forwarding偏移 next_forwarding_offset += obj->class->size; //遞歸複製引用對象,遞歸是深度優先 for (int i = 0; i < obj->class->num_fields; i++) { copy(*(object **) ((void *) obj + obj->class->field_offsets[i])); } return forwarding; } return obj->forwarding; }
我的以爲「轉發指針(Forwarding Pointer)」在複製算法中仍是一個比較重要的概念
轉發指針,指的時複製時,在原對象裏保留新對象的指針。爲何要保留這個指針呢?
由於須要複製的不僅是對象,對象的引用關係也須要複製。好比下圖,對象ACD都須要複製,且只複製了對象A時,實際上覆制的對象A'(一撇)引用的CD仍是未複製的
在全部活動對象都複製完畢後,須要將引用的地址調整爲複製後的對象地址;只須要遍歷一邊to空間,找到引用對象的forwarding pointer更新便可
void adjust_ref() {
int p = 0; //遍歷to,即複製的目標空間 while (p < next_forwarding_offset) { object *obj = (object *) (p + to); //將還指向from的引用更新爲forwarding pointer,即to中的pointer for (int i = 0; i < obj->class->num_fields; i++) { object **field = (object **) ((void *) obj + obj->class->field_offsets[i]); if ((*field) && (*field)->forwarding) { *field = (*field)->forwarding; } } //順序訪問下一個對象 p = p + obj->class->size; }
}
以上就是對複製算法的說明
堆利用率較低,由於在複製算法下,只有一半的內存用來存儲對象
https://github.com/kongwu-/gc_impl/tree/master/copying