C++实现垃圾回收,不小心把老文章删除了,重新补发一个

C++系统下,内存直接操作是一个艺术,但是在实际项目开发中,艺术这种东西毕竟是少数需求,最重要的还是系统的鲁棒性,而内存泄露的问题,可以说一直在影响着C++开发,为此,有人采用智能指针,有人用引用计数技术,但是这些终究都有先天性的缺陷,比如他们都存在着相互交叉引用的问题,需要wake_ptr类似的处理,真正要做到自然好用的处理内存和对象生存管理,真命天子就是-------GC(垃圾回收)!C++0x规范传闻要支持GC,但是貌似马上年了,x规范难道要变成x?        其实利用现在标准C++,实现一个支持GC的系统也不是什么难事,在下面的文章里,我准备和大家讨论实现一个GC系统的要素和过程。

目标:
                对象new出来后,如果没有被任何其它对象引用,系统应该自动释放。
思路:
                标志 - 遍历,获取释放对象
前提:
                1 。所有GC对象派生自GCObject
                2 。GCObject需要有类似Reflection的完整类型信息
                3 。不能手工调用任何GCObject的删除
详细描述:
                GCObject 定义了可垃圾回收对象的接口,首先我们要实现一个类型信息,用来描述一个类他的成员变量的类型和地址,这样,我们
在垃圾回收的时候,才知道一个对象引用了哪些别的对象。成员变量和类型信息描述类似如下
                struct ClassType;
 
                struct Member
                {
                       INT_PTR   Offset;// 成员变量的地址偏移
                       struct ClassType*     Type;// 成员变量的类型
                };
 
                struct ClassType
                {
                       int GetContainNumber();
                       GCObject* GetContainObject();
                       vector<Member*>          Members;// 类的成员变量集合
                };
 
                现在我们只需要找一个方法,把每个类的类型信息抽取出来,填入到ClassType的Members里面去,C ++ 里面Macro和Template是做
这种事情的利器,他甚至不只是可以取得成员变量的偏移,还可以获得函数地址,从而实现invoke等完整的反射功能,具体怎么做,可以去我
blog 上找C ++ 实现反射的文章,里面有我实现的最简单例子。
                好了,上面的准备结束后,我们进入真正的GC实现了,标注 - 遍历模式的GC,思路非常简单清晰,描述大致如下:首先把所有GCObject
标志成为不可到达,然后,从Root根对象开始,递归向下遍历,到达一个对象,标志为可到达,最后,检查整个对象列表,被标志为不可到达
的对象,你就可以放心大胆地释放删除了。
                GCObject 的大致定义如下:
                class GCObject
                {
                       GCObject*                      Next;
                       GCObject*                      Prev;
 
                       static vector<GCObject*>              Roots;
 
                       bool                                Reachable;
 
                       virtual ClassType*    GetClassType() = 0;
                       GCObject()
                       {
                               // 连接本对象到GCObject的大链表
                       }
                       void GC();
                };
                好了,我们可以写GCProcess了:
                void GCProcess()
                {
                       // 首先把所有对象标志不可到达
                       for each( GCObject* pMem in GCObjects )
                       {
                               pMem->Reachable = false;
                       }
 
                       // 从所有根对象开始递归标志哪些对象可到达
                       for each Roots
                               Root.GC();
 
                       // 清理不可到达对象
                       for each( GCObject* pMem in GCObjects )
                       {
                               if( pMem->Reachable == false )
                               {
                                       //pMem 断开对象链表
                                       DestroyGCObject(pMem);
                               }
                       }
                }
 
                void GCObject::GC()
                {
                       Reachable = true;
                       ClassType* pType = this->GetClassType();
                       for each( Member* pMem in pType->Members )
                       {
                               GCObject* pMbObj = (GCObject*)(((BYTE*)this) + pMem->Offset);
                               if( pMbObj->Reachable==true )
                                       continue;
                               pMbObj->GC();
                       }
                }
 
                好了,简单吧?不过还有一点小问题,那就是累死Vector一类的容器,里面如果装了GCObject,这个也需要走GC。所以,我们要自己
实现一些容器类,让能清晰知道这个容器内装的是GCObject,如此,我们可以改写
                struct ClassType
                {
                       int GetContainNumber();// 获得成员外引用对象数量
                       GCObject* GetContainObject(int i);// 获得成员外引用对象
                       vector<Member*>          Members;// 类的成员变量集合
                };
                工作来了,为vector ,map,list 实现不同版本的ClassType吧!
                目前为止,看起来GC本身的工作,我们已经搞定了,但是GCProcess () 这个函数看起来太多的对象遍历,效率肯定高不了,我们需要
严格控制调用频率,而且这个GCProcess调用的时候,我们恐怕需要别的对象操作都停下来,否则这个GC就不那么可靠拉!有点沮丧,
个比较让人讨厌,能不能想办法优化他呢?
                优化的方法肯定有的,让我们来引入代的概念。做个假设吧,一个对象如果越久没有释放,那么他越不可能被释放,这个可以理解吧?
很多时候,我们就要一个临时对象用一下,马上就丢掉了。好,我们把所有GCObject分成几个可能释放的等级,也就是代数。代越小,表示
他越产生时间新,被收集可能越大。
                改写下GCObject的定义:
                class GCObject
                {
                       GCObject*                      Next;
                       GCObject*                      Prev;
 
                       static GCObject                      Gen0;// 第一代
                       static GCObject                      Gen1;// 第而代
                       static GCObject                      Gen2;// 第三代
 
                       int                                           GenID
 
                       bool                                Reachable;
 
                       virtual ClassType*    GetClassType() = 0;
                       GCObject()
                       {
                               // 连接本对象到GCObject的大链表
                       }
                       void GC();
                };
                我们当对象new出来后,缺省放入第一代对象列表里面,当内存不够,或者时间流逝了一定时间,我们需要做垃圾回收的时候,GC函数
最先开始遍历Gen0收集垃圾,也采用标志 - 遍历方式,这样,我们可以把能收集对象收集了,但是没有收集的对象,我们就要把他们从Gen0里
面取出来,放入Gen1里面,如果此时GC收集到了足够的内存,我们可以停止GC操作了,否则,我们需要对Gen1进行标注 - 遍历方式垃圾收集,
后面再把Gen1收集剩下的放入Gen2了,如此向下迭代,如果所有代的垃圾都收集完毕,还是不能满足分配,恭喜你,系统无药可救, 你可以
throw OutOfMemory 了。
 
                下面是修改后的GC处理过程:
                void GCObject::GC( int CurGen )
                {
                       if( GenID>CurGen )
                               return;
                       Reachable = true;
                       ClassType* pType = this->GetClassType();
                       for each( Member* pMem in pType->Members )
                       {
                               GCObject* pMbObj = (GCObject*)(((BYTE*)this) + pMem->Offset);
                               if( pMbObj->Reachable==true )
                                       continue;
                               pMbObj->GC();
                       }
                }
 
                void GCProcess()
                {
                       for each( GCObject* pMem in Gen0 )
                       {
                               pMem->Reachable = false;
                       }
 
                       for each( GCObject* pMem in Gen0 )
                       {
                               pMem->GC();
                       }
 
                       for each( GCObject* pMem in Gen0 )
                       {
                               if( pMem->Reachable == false && pMem->GenID<1 )
                               {
                                       //pMem 断开对象链表
                                       DestroyGCObject(pMem);
                               }
                       }
 
                       // 把所有gen0的GCObject挂入gen1内
                       RiseGen( Gen0 , Gen1 );
 
                       if( SpaceEnough() )
                               return;
 
                       ..........
 
                       throw OutOfMemory;
                }
 
                通过分代,我们可以大大减少垃圾回收中没有必要遍历数量,这样可以让程序的GC性能大幅度提升。
                好久没写过文章了,甚至代码都很手生,这里描述的是一个示例性质的伪代码,重要的是说明白GC的原理和大致优化方式。而我本人对
GC 的了解也只是皮毛,上次写试验代码也是年前,其中的谬误不可避免,欢迎高手前来赐教。
                BTW: 最好的垃圾回收是语言支持的GC,最近几年做项目逻辑,我多用 .net ,所以根本不用考虑GC的实现细节,建议真要做GC系统的人
先看看是不是用 .net 就可以了。MS的牛人,绝对会比我们做的NB的多,而且,他能在编译期检测很多东西,肯定要比我们能做的优化,要多
很多了。
上次的原文,貌似地址还在
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值