GC算法实践(一) 内存分配篇

来源:互联网 发布:尔雅网络课app 编辑:程序博客网 时间:2024/06/05 23:07

要实现自己的垃圾回收算法,首先要实现一套自己的内存分配方法,把内存的管理权掌握在自己手里,而不是每次都调用系统函数,向操作系统要一小块内存,否则垃圾回收就无从谈起。思路主要是:

一开始申请一块大的内存,后面每次程序需要内存就从这个内存块中分配,不够了再想办法处理(垃圾回收、压缩、分配更大的内存等)。

上面申请的一块大的内存一般叫做堆(heap)。名字叫什么不要紧,反正就是内存的一部分,申请下来后都归自己管理了。

很多软件都用类似的方法来管理内存,借鉴市场经济的做法,从多次的少量”购买“变成一次”大量批发“,的确是一种进步。

1. 关键数据结构与基本操作

1.1 堆的结构以及基本操作

目前我们只需要知道堆(heap)的起始地址、堆中空闲内存的起始地址、堆的结束地址即可。

定义一个数据结构来表示堆(各字段含义如其名字所示):

typedef struct _Heap {    char *start_addr;    char *free_addr;    char *end_addr;}Heap;

示意图如下:

这里写图片描述

创建一个堆可以用如下方法:

Heap* new_heap(uint size) {    Heap *heap = (Heap*)malloc(sizeof(Heap)+size);    heap->start_addr = (char*)heap + sizeof(Heap);    heap->free_addr = heap->start_addr;    heap->end_addr = heap->start_addr + size;    return heap;}

够简单了吧,无须解释。

1.2 对象的结构以及基本操作

堆是用来动态分配内存给对象用的,所以我们还得需要知道对象的结构。参考“自制Java虚拟机”中对象的结构,当前为了测试方便,定义对象的结构如下(包含对象的一些头部信息以及对象的字段指针):

typedef struct _Object {    char flag;    ushort length;    char* fields;}Object;

示意图如下:

这里写图片描述

关于对象,有如下基本操作:

// 1.获取整个对象的占用空间大小#define OBJ_SIZE(obj) (sizeof(Object) + (obj->length<<2))// 2.给对象的一个int类型字段赋值#define OBJ_SET_INT(obj,offset,value) *(int*)(obj->fields + ((offset)<<2)) = (value)// 3.给对象的一个Object(引用/指针)类型字段赋值#define OBJ_SET_OBJ(obj,offset,value) *(Object**)(obj->fields + ((offset)<<2)) = (value)// 4.取一个int类型的字段#define OBJ_GET_INT(obj,offset) *(int*)(obj->fields + ((offset)<<2))// 5. 取一个Object(引用/指针)类型的字段#define OBJ_GET_OBJ(obj,offset) *(Object**)(obj->fields + ((offset)<<2))

目前为了方便测试,对象的字段只有2种数据类型,intObject

2.分配内存

2.1 内存分配方法

C语言中的malloc函数返回的是所申请内存的起始地址(指针),指针类型的转换需要用户自己完成。我们也可以参考这一方案,从堆中找到空闲内存,如果足够的话就返回空闲内存的起始地址,不够的话,目前为了简化,直接退出应用。

分配内存的函数实现如下:

/** * 从指定堆分配size大小的内存 * @return char* 分配内存的首地址 * @params Heap* heap 堆 * @params uint size 内存大小(byte)  */char* alloc_memory(Heap* heap, uint size) {    char *addr = NULL;    char *next_free_addr = heap->free_addr + size;    if (next_free_addr > heap->end_addr) {        printf("Error! Out Of Memory!\n");        exit(1);        }    addr = heap->free_addr;    heap->free_addr = next_free_addr;    return addr; }

给对象分配内存的示意图:
这里写图片描述

2.2 创建对象

解决了给对象分配内存的问题,接着就是要创建对象。创建对象的基本步骤如下:

  1. 计算该对象所需空间大小
  2. 分配内存空间
  3. 初始化对象的头部信息以及字段内容
/** * 从当前可用堆中创建一个对象  * @return Object* 新创建对象的引用  * @params ushort length 对象字段数  */Object* new_object(ushort length) {    uint field_size = length<<2;    Object *obj;    obj = (Object*)alloc_memory(cur_heap, field_size + sizeof(Object));    obj->flag = 0;    obj->length = length;    obj->fields = (char*)obj + sizeof(Object);    memset(obj->fields, 0, field_size);    return obj;}

其中,cur_heap是当前可用的堆(活动堆)。

3.测试辅助方法

给对象分配内存的基本功能已经实现了,接下来需要准备测试。

3.1 打印对象

为了方便调试、测试,需要打印出对象的内容。

为了方便测试,约定对象的第一个字段数据类型为int,后面的字段类型均为Object实际使用中需要用合适的方法来识别对象的字段类型是否为对象(引用/指针)。

void _dump_object(Object *obj, int depth) {    int i, index;    Object *sub_obj;    if (NULL == obj) {        printf("null\n");        return;    }    for(i=0; i<depth; i++) {        printf("\t");    }    if (depth > 0) {        printf("=>");    }    printf("object[%d] at: %p, flag=%d, length=%d, ", OBJ_GET_INT(obj, 0), obj, obj->flag, obj->length);    printf("fields[0]=%d\n", OBJ_GET_INT(obj, 0));    for(index=1; index<obj->length; index++){        sub_obj = OBJ_GET_OBJ(obj, index);        if (NULL != sub_obj) {            _dump_object(sub_obj, depth+1);        }    }}void dump_object(Object *obj) {    _dump_object(obj, 0);}

3.2 打印堆(heap)内容

为了方便调试、测试,需要打印出堆中的对象。

由于是连续分配内存,每个对象也可以计算出总的大小,堆可以作为一个链表来遍历。

void dump_heap(Heap *heap) {    Object *tmp_obj = NULL;    char *next_addr = NULL;    uint sz_object = sizeof(Object);    printf("----------------------------- heap data ------------------------------\n");    if (NULL == heap) {        printf("null\n");        return;    }    printf("heap: [start_addr]=%p, [free_addr]=%p, [end_addr]=%p\n", heap->start_addr, heap->free_addr, heap->end_addr);    if (heap->free_addr == heap->start_addr) {        printf("heap has no object!\n");        printf("----------------------------- End ------------------------------------\n");        return;    }    next_addr = heap->start_addr;    while(next_addr < heap->free_addr){        tmp_obj = (Object*)next_addr;        dump_object(tmp_obj);        next_addr += (sz_object + (tmp_obj->length << 2));    }    printf("----------------------------- End ------------------------------------\n");}

4. 测试

4.1 内存分配的测试函数

 void test_alloc_memory() {    Object *objects[6];    int obj_len[6] = {3,2,4,2,3,2};    int i;    for(i=0; i<6; i++) {        objects[i] = new_object(obj_len[i]);        OBJ_SET_INT(objects[i], 0, i);    }    OBJ_SET_OBJ(objects[1], 1, objects[4]); // objects[1]->objects[4]    OBJ_SET_OBJ(objects[0], 1, objects[1]); // objects[0]->objects[1]    OBJ_SET_OBJ(objects[0], 2, objects[5]); // objects[0]->objects[5]}

该测试函数中一共创建了6个对象,它们之间的关系为:objects[0] => objects[1] => objects[4], objects[0] => objects[5],objects[2]和objects[3]都是孤立的。

它们在内存中的逻辑关系示意图如下:

这里写图片描述

4.2 其它相关代码

#define HEAP_SIZE 1024 // 定义堆的大小typedef unsigned short ushort;typedef unsigned int uint;Heap *cur_heap, *free_heap; // 作为全局变量; cur_heap表示当前活动堆// 初始化堆void init_heap() {    cur_heap  = new_heap(HEAP_SIZE);    free_heap = new_heap(HEAP_SIZE);}

4.3 执行测试

main函数中编写测试代码:

// main函数int main() {    init_heap(); // 0.初始化堆    printf("after alloc...");    test_alloc_memory(); // 测试动态创建对象    printf("\ncur_heap:\n");    dump_heap(); // 打印堆中的对象信息    return 0;}

4.4 测试数据与结果

运行结果如下:

这里写图片描述

将打印结果与我们的预期(参考test_alloc_memory中的代码)相比,可知对象的内存分配正常。

5.总结

用C语言实现了自定义内存分配算法(相对于调用malloc而言),只需一次调用系统函数malloc,申请一块较大的内存(Heap),后续的动态内存分配均在该堆中进行,运行正常。无需反复调用系统函数进行分配内存。

原创粉丝点击