c语言代码大全复制,垃圾回收算法实现之 - 复制算法(完整可运行C语言代码)...

GC 复制算法(Copying GC)是 Marvin L. Minsky 在 1963 年研究出来的算法。说得简单点,就是只把某个空间里的活动对象复制到其他空间,把原空间里的所有对象都回收掉。这是一个相当大胆的算法。在此,我们将复制活动对象的原空间称为 From 空间,将粘贴活动对象的新空间称为 To 空间。

本文实现的是 Robert R. Fenichel 与 Jerome C. Yochelson 研究出来的 GC 复制算法,使用C语言实现

名词解释

对象

对象在GC的世界里,代表的是数据集合,是垃圾回收的基本单位。

指针

可以理解为就是C语言中的指针(又或许是handle),GC是根据指针来搜索对象的。

mutator

这个词有些地方翻译为赋值器,但还是比较奇怪,不如不翻译……mutator 是 Edsger Dijkstra 琢磨出来的词,有“改变某物”的意思。说到要改变什么,那就是 GC 对象间的引用关系。不过光这么说可能大家还是不能理解,其实用一句话概括的话,它的实体就是“应用程序”。

mutator的工作有以下两种:生成对象

更新指针mutator 在进行这些操作时,会同时为应用程序的用户进行一些处理(数值计算、浏览网页、编辑文章等)。随着这些处理的逐步推进,对象间的引用关系也会“改变”。伴随这些变化会产生垃圾,而负责回收这些垃圾的机制就是 GC。

GC ROOTS

GC ROOTS就是引用的起始点,比如栈,全局变量

堆(Heap)

堆就是进程中的一段动态内存,在GC的世界里,一般会先申请一大段堆内存,然后mutatar在这一大段内存中进行分配

1460000022062600

活动对象和非活动对象

活动对象就是能通过mutatar(GC ROOTS)引用的对象,反之访问不到的就是非活动对象。

准备工作

在复制算法中,使用顺序内存分配(sequential allocation)策略,顺序分配流程如下图所示

1460000022069043

维护一个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空间容量保持一致。

复制算法的流程如下图所示:

1460000022069044

初始化堆

复制算法中,需要将堆一分为二,一半作为from,一半作为tovoid 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 pointerobject *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);

}

复制算法流程如下:

1460000022069073

1460000022069074

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

个人觉得“转发指针(Forwarding Pointer)”在复制算法中还是一个比较重要的概念

转发指针,指的时复制时,在原对象里保留新对象的指针。为什么要保留这个指针呢?

因为需要复制的不只是对象,对象的引用关系也需要复制。比如下图,对象ACD都需要复制,且只复制了对象A时,实际上复制的对象A'(一撇)引用的CD还是未复制的

1460000022069047

1460000022069092

调整引用

在所有活动对象都复制完毕后,需要将引用的地址调整为复制后的对象地址;只需要遍历一边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;

}

}

以上就是对复制算法的说明

优点吞吐量高,不需要遍历全堆,只需要处理活动对象

分配速度快,和free-list分配法相比,顺序分配不需要搜索free-list,只需要移动free pointer即可

不会有碎片化的问题,因为每次复制都将存活对象从from复制到to的一端

缺点

堆利用率较低,因为在复制算法下,只有一半的内存用来存储对象

完整代码

相关文章

参考《垃圾回收的算法与实现》 中村成洋 , 相川光 , 竹内郁雄 (作者) 丁灵 (译者)

《垃圾回收算法手册 自动内存管理的艺术》 理查德·琼斯 著,王雅光 译

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值