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