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种数据类型,int
和Object
。
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 创建对象
解决了给对象分配内存的问题,接着就是要创建对象。创建对象的基本步骤如下:
- 计算该对象所需空间大小
- 分配内存空间
- 初始化对象的头部信息以及字段内容
/** * 从当前可用堆中创建一个对象 * @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),后续的动态内存分配均在该堆中进行,运行正常。无需反复调用系统函数进行分配内存。
- GC算法实践(一) 内存分配篇
- gc和内存分配
- JVM之---GC内存分配
- JVM内存分配与GC
- 垃圾回收GC:.Net自动内存管理 上(一)内存分配
- Java虚拟机详细解析--JVM类加载过程+内存分配+GC算法+垃圾回收器分类
- JVM(一)——GC,内存分配和垃圾回收
- Java GC 机制与内存分配策略(《深入理解Java虚拟机》一书的整理笔记)
- Java 堆内存分配与GC
- 我是菜鸟:GC和内存分配策略
- java gc的垃圾处理和内存分配
- java中GC回收和内存分配
- JAVA GC 与 内存分配策略
- Java GC 机制与内存分配策略
- Java GC 机制与内存分配策略
- Java GC 机制与内存分配策略
- Java GC 机制与内存分配策略
- GC 垃圾收集器 内存分配 读笔
- Python基础笔记
- HDFS上数据保存到Hbase运行报错:NoClassDefFoundError: org/apache/hadoop/hbase/HBaseConfiguration
- hash算法以及解决冲突的方法
- 在Activity之间传递简单数据
- centos7.3单机快速安装openstack
- GC算法实践(一) 内存分配篇
- Qt 图片缩放无锯齿处理
- 中文分词分析之PDF批量转化为文本
- 初学网络
- 最终实现混合app仿淘宝app自动识别淘口令 第一篇:mui混合开发获取原生剪切板内容
- Zynq 7000裸机的lwip 样例程序echo server 实验
- spark convert RDD[Map] to DataFrame
- 根据前序遍历和中序遍历,后序遍历和中序遍历重构二叉树
- QT5每日一学(二)编写QT多窗口程序