上一篇文章中,我们实现了自定义分配内存,有了这个基础,我们可以开发垃圾回收算法了。GC算法有很多种,如引用计数法、标记-清除算法、复制算法、分代回收算法等,也有综合运用几种算法的。PHP用到了引用计数算法,Java用到了复制算法和分代回收算法。由于引用计数算法需要频繁更新引用计数,目前暂不研究;标记-清除算法则因为清除后造成大量内存碎片不好管理,目前只研究标记(标记出活动对象);复制算法是本篇研究的重点。
1.标记-清除算法、复制算法简介
1.1 根对象
首先需要理解一个基本概念:根对象。根对象是程序中可以直接访问到的对象,比如:
有两个对象
a
,a
有成员b
,访问对象a
直接用a
就行,而访问对象b需要通过a->b
,那么对象a
就是根对象,对象b
由于只能通过a->b
来访问,所以不是根对象。
根对象可以是全局变量、函数调用栈上的变量等。
1.2 标记-清除算法
标记-清除算法的大致思想如下:
标记阶段:遍历根对象及其引用的对象。假设每个对象都有个标记位
flag
,对根对象集合中的每个根对象,从根对象出发,对可以访问到的每个对象的标记位flag
设为1(活动对象)。清除阶段:遍历堆,将非活动对象所占空间设为可用。遍历堆,将标记位
flag
等于0的对象(即垃圾)所占据的空间设为可用。
标记-清除算法的清除阶段 后,产生很多内存碎片,管理比较麻烦。
1.3 复制算法
复制算法的大致思想如下:
把给对象分配内存的堆(heap)分成大小相等的两部分,或者申请2块大小相同的堆,其中一个堆称为
From空间
,另一个称为To空间
。首次给对象分配内存时,活动堆为
From空间
,从From空间
分配;触发GC时,采用标记-清除算法中的标记算法遍历活动对象,把活动对象复制到To空间
,然后就把To空间
当做当前活动堆。To空间
满触发GC时,把活动对象复制到From空间
,如此交替进行。
复制算法的缺点是内存空间利用效率低,只有50%。
2.标记对象
先构建一个测试场景,代码如下:
Object *root[2]; // simulate root objects collection
void test_alloc_memory() {
Object *objects[6];
int obj_len[6] = {
3,2,4,2,3,2};
int i;
for(i=0; i<