标签:
在我写C++代码的那些时间里,我没有写过垃圾回收器,也没有实现过自己的内存分配器,这方面的文章倒是看了不 少。比如我在写C#代码时只管new而不需要释放,我也明白有个垃圾回收器在那帮我回收那些堆上的对象,但具体的实现也没有深究。这段时间我突然想起了以 前在某个地方看过关于一个小型垃圾回收器实现的文章,那是一篇翻译文章,于是搜了下找到了源代码,作者是Google公司一哥们(套近乎啊,好像认识似的)。今天韦哥决定分析一下这个实现,因为一般而言,分析比自己厉害得多的人的作品,总会有些收获。
源码在github上,顺藤摸瓜就能找到他解释这个实现的文章,点击这里:https://github.com/munificent/mark-sweep
这里为简单化,我们把变量使用的每一块内存都叫对象。我们知道,栈上的对象在函数调用过程中或者调用结束后全部被释放掉,可以理解为这是操作系统的ABI规 范,因此我们不需要管理栈内存,垃圾回收器管理的是堆内存。那垃圾回收器怎么知道哪些对象可以回收呢?应该是那些从栈上无法直接或间接访问到的堆上的对象可以被回收。那又怎么判定从栈上无法直接或间接访问到某个堆上对象呢?这也是我以前比较困惑的地方。比如我在某个函数里边写了一行:“A* p = new A();”,在A对象产生的时候,应该在某个地方有个记录,保存了p的值。在函数退出时,记录里p的值被清理,但是它所指向的内存对象还存在,等到垃圾回收器进行回收检查的时候,它先要把能从栈上访问到的内存对象标记为被使用中,以免清理阶段错误释放了该内存对象。对于p,因为在记录里被清理了,在标记阶段,从栈上已经无法访问它,因此没有被标记为使用中,在清理阶段这个对象就会被释放掉。那么问题又来了,既然从栈上已经无法访问到p指向的对象,又如何释放呢?其实是有另外一个路径可以访问到它的,垃圾回收器保存一个根对象,通过这个根对象可以访问到所有已分配的内存对象,但这些对象可能已经没有栈上变量 指向它了,垃圾回收器正是以这个为依据来进行内存释放。
在作者的实现中,有两个结构体比较重要,就是虚拟机结构体,声明如下:
typedef struct {
Object* stack[STACK_MAX];
int stackSize;
/* The first object in the linked list of all objects on the heap. */
Object* firstObject;
/* The total number of currently allocated objects. */
int numObjects;
/* The number of objects required to trigger a GC. */
int maxObjects;
} VM;
从栈上能访问到的对象是那些从VM结构体中stack变量可以直接或间接访问到的对象,这里的对象就是Object类型的,Object声明如下:
typedef struct sObject {
ObjectType type;
unsigned char marked;
/* The next object in the linked list of heap allocated objects. */
struct sObject* next;
union {
/* OBJ_INT */
int value;
/* OBJ_PAIR */
struct {
struct sObject* head;
struct sObject* tail;
};
};
} Object;
Object结构体里有个next成员指向下一个对象,因此有间接访问到这么一说法。
VM结构体中stack变量决定了哪些对象还在被引用,比如上面提到的p,在刚刚给它分配对象时,从stack可以访问到它,但是当它离开所在作用域又没有其它变量引用过p所指向的内存对象时,从stack变量就已经无法访问到它了。但在p所指向内存对象被垃圾回收器释放之前,从VM结构体里的firstObject一步一步往下走,始终能找到这个内存对象。
顾名思义,mark-sweep算法分为两个阶段,即mark和sweep阶段,中文就是标记和清理阶段。有了以上的理解之后,我们就明白,这个实现中为什么在调用函数gc时依次调用了markAll(vm)和sweep(vm)。
void gc(VM* vm) {
int numObjects = vm->numObjects;
markAll(vm);
sweep(vm);
vm->maxObjects = vm->numObjects * 2;
printf("\nCollected %d objects, %d remaining.\n", numObjects - vm->numObjects,
vm->numObjects);
}
每个Object对象有一个标记位,在这个对象初始化时标记为置0,表示可以被回收(是不是显得有点奇怪,会不会被错误释放?)。我刚开始直觉上也感觉不对,新对象不是在被使用吗,应该为1才对啊。而实际其思路是这样的:首先认为所有对象都可以被回收,然后在你回收之前,遍历stack变量,把从它能到达的对象都标记为1,这就是标记阶段,再执行清理,清理不再是遍历stack变量,而是遍历firstObject变量。对于那些遍历stack变量时因为没有直接或间接引用而不可达的变量,在清理阶段遍历firstObject却能找到它,并发现它的标记为是0,于是把它释放掉。对于那些标记为1的变量,把它重新置0回到初始状态,表示可回收,等待下次gc调用。
标签:
原文地址:http://www.cnblogs.com/woshiweige/p/4537451.html