说明:代码实现借鉴这边文章
查找及回收无用对象的方法有很多种,最简单也是最早的一种方法,叫“标记-清除法”。 过程:
从根对象开始,遍历整个对象图。每访问一个对象,就把一个标记位设成true。
一旦完成遍历,找出所有没有被标记过的对象,并清除掉它们。 假设我们正在写一门小语言的解释器。它是动态类型的,有两种对象:int以及pair。下面是一个定义对象类型的枚举:
#include
enum Obj_Type
{
OBJ_INT,
OBJ_PAIR
};
实现对象类型:
struct _Object
{
Obj_Type type;
unsigned char marked;//标记标志,如果被标记,说明可达。
//union
//{
int value;//存储的值
struct
{
_Object* pHead;
_Object* pTail;
};
//};
_Object *pNext;
};
现在我们可以把它们封装到一个小型虚拟机的结构里了。这个虚拟机在这的作用就是持有一个栈,用来存储当前使用的变量。很多语言的虚拟机都要么是基于栈的(比如JVM和CLR),要么是基于寄存器的(比如Lua)。不管是哪种结构,实际上它们都得有一个栈。它用来存储本地变量以及表达式中可能会用到的中间变量。
#define MAX_SIZE 256 //堆栈最大数目
#define INITIAL_GC_THRESHOLD 128 //触发gc的对象数量
typedef struct _tagVM
{
_Object* objs[MAX_SIZE];
int max_objs;//产生的最大对象的数目,超过调用gc
int num_objs;//记录现在的对象数目
//对虚拟机层面,所有的对象应该都是可见的,从用户角度,有些对象可能没有任何引用指向它,对象也就
//不可见,所以
//用链表记录通过虚拟机产生的所有对象。
_Object *pFirstObj;//链表的头指针
_Object *pLastObj;//链表的尾指针
int objs_size;//堆栈大小或者堆栈指针
}VM;
创建虚拟机的函数:
VM* new_VM()
{
VM *pVm=(VM *)malloc(sizeof(VM));
pVm->objs_size=0;
_Object *pObj=create_obj(OBJ_INT);
pObj->value=0;
pObj->marked=1;
pObj->pHead=NULL;
pObj->pTail=NULL;
pObj->pNext=NULL;
pVm->pFirstObj=pObj;//NULL;第一个节点不使用,
pVm->pLastObj=pVm->pFirstObj;
pVm->max_objs=INITIAL_GC_THRESHOLD;
pVm->num_objs=0;
return pVm;
}
释放虚拟机函数:
void delete_VM(VM *pVM)
{
_Object *pObj=pVM->pFirstObj;
while (pObj!=NULL)
{
printf("%i\t",pObj->value);
_Object *tmp=pObj->pNext;
delete_Obj(pObj);
pObj=tmp;
}
free(pVM);
pVM=NULL;
}
有了虚拟机后,我们需要对它的栈进行操作:
void push(VM* pVM,_Object *pObj)
{
assert(pVM->objs_size<=MAX_SIZE ,"Stack overflow!");
pVM->num_objs++;
pVM->pLastObj->pNext=pObj;
pVM->pLastObj=pObj;
pVM->objs[pVM->objs_size++]=pObj;
if (pVM->num_objs>pVM->max_objs)
{
gcVM(pVM);
}
}
_Object * pop(VM*pVM)
{
assert(pVM->objs_size>0 ,"Stack underflow!");
return pVM->objs[--pVM->objs_size];
}
产生对象的函数:
_Object * create_obj(Obj_Type type)
{
_Object *pObj=(_Object *)malloc(sizeof(_Object));
pObj->type=type;
pObj->marked=0;
pObj->pNext=NULL;
return pObj;
}
_Object * new_obj(Obj_Type type)
{
_Object *pObj=create_obj(type);
return pObj;
}
有了它我们就可以把不同类型的对象压到栈里了:
void push_int(VM* pVM,int value)
{
_Object *pObj=new_obj(pVM,OBJ_INT);
pObj->value=value;
push(pVM,pObj);
}
_Object * push_pair(VM* pVM,int value)
{
_Object *pObj=new_obj(pVM,OBJ_PAIR);
pObj->value=value;
pObj->pTail=pop(pVM);
pObj->pHead=pop(pVM);
push(pVM,pObj);
return pObj;
}
如果我们有个解析器和解释器来调用这些函数,就是一门完整的语言了。下面是标记-清除过程: 第一个阶段是标记阶段,
void mark(_Object *pObj,unsigned char marked)
{
if (pObj->marked) return;//防止pair类型相互引用,造成环回,形成无穷递归。
printf("%i\t",pObj->value);//调试输出
pObj->marked=marked;
if (pObj->type==OBJ_PAIR)//pair类型,进行递归
{
mark(pObj->pHead,marked);
mark(pObj->pTail,marked);
}
}
void mark_all(VM*pVM)
{
for (int i=0;iobjs_size;++i)
{
//printf("%i\t",pVM->objs[i]->value);
if (pVM->objs[i]->type==OBJ_PAIR)//堆栈中的非Pair节点,认为不可达
{
mark(pVM->objs[i],1);
}
}
}
下一个阶段就是遍历所有分配的对象,释放掉那些没有被标记的了,
void sweep(VM* pVM)
{
_Object *pObj=NULL,*pFrontObj=NULL;
pObj=pFrontObj=pVM->pFirstObj;
while (pObj!=NULL)
{
if (!pObj->marked)
{
printf("%d\t",pObj->value);//调试输出
pFrontObj->pNext=pObj->pNext;//删除链表节点,调整前后指针指向
_Object *tmp=pObj;
pFrontObj=pObj=pFrontObj->pNext;
free(tmp);
pVM->num_objs--;//目前对象数目减一
}
else
{
pFrontObj=pObj;
pObj=pObj->pNext;
}
}
}
最后,有了一个垃圾回收器:
void gcVM(VM*pVM)
{
mark_all(pVM);
sweep(pVM);
//每次回收之后,我们会根据存活对象的数量,更新maxOjbects的值。
//这里乘以2是为了让我们的堆能随着存活对象数量的增长而增长。
//同样的,如果大量对象被回收之后,堆也会随着缩小
pVM->max_objs = pVM->num_objs * 2;
}
然后,我们在new_obj根据对象产生的数目来触发gc。 我们来产生一些对象来测试gc:
int _tmain(int argc, _TCHAR* argv[])
{
VM *pVM=new_VM();
for (int i=0;i<10;++i)//产生一些对象
{
push_int(pVM,10);
push_int(pVM,15);
push_pair(pVM,20);//使堆栈中丢失前面的两个对象指针
push_int(pVM,30);//不可达
}
printf("mark_all:\n");
mark_all(pVM);
printf("sweep:\n");
sweep(pVM);
printf("delete_VM:\n");
delete_VM(pVM);
getchar();
return 0;
}
最终如下: