代码改变世界

不是GC打酱油,是人打酱油

2011-09-22 22:00  乱世文章  阅读(258)  评论(0编辑  收藏  举报
 

不是GC打酱油,是人打酱油

之前在一个技术群里看到有人讨论内存释放问题,引发对GC的讨论,大体结论是GC不可靠,GC是打酱油的。

之所以得出这个结论,是因为我们大多时候使用的是GC.Collect(),同时并未提供大内存对象的析构函数并在析构函数中释放内存。

如有类:

public class BufWrapper

{

    private byte[] buf;

    public BufWrapper()

    {

        buf=new byte[1024*1024*2];

    }

    ~BufWrapper()

    {

        buf=null;

    }

}

 

对此写测试代码:

BufWrapper br=null;

while(true)

{

    br=null;

    br=new BufWrapper();

}

 

这样的代码必定导致内存问题,原因在于虽然每次循环将上次创建的br设置为null使其引用数得以置零,但 .net的垃圾回收机制在默认情况下并不立即执行其析构函数,从而此对象中的buf不能被立即释放。所以,考虑为测试代码增加GC.Collect():

BufWrapper br=null;

while(true)

{

    br=null;

    br=new BufWrapper();

    GC.Collect();

}

测试会发现,内存有所控制,但长时间观察,仍然呈波动上升趋势,而这应该正是大家怀疑GC的原因所在。其实,GC.Collect()也并不保证垃圾对象被立即回收。所以,一般建议为对象实现IDisposable接口,通过代码主动释放:

public class BufWrapper:IDisposable

{

    private byte[] buf;

    public BufWrapper()

    {

        buf=new byte[1024*1024*2];

    }

    ~BufWrapper()

    {

        this.Dispose();

    }

    public virtual void Dispose()

    {

        buf=null;

    }

}

测试代码:

BufWrapper br=null;

while(true)

{

    if(br!=null)br.Dispose();

    br=new BufWrapper();

}

 

测试发现,以上代码不存在内存问题。

但是,这里就存在一个问题,如果现有对象并未实现IDisposable接口,或实现的接口方法中并未释放资源怎么办?GC.Collect()不能保证立即释放资源,怎么办?

其实GC还有一个方法:GC.WaitForPendingFinalizers()。使用以下代码(甚至BufWrapper不提供析构函数):

public class BufWrapper

{

    private byte[] buf;

    public BufWrapper()

    {

        buf=new byte[1024*1024*2];

    }

}

测试代码:

BufWrapper br=null;

while(true)

{

    br=null;

    br=new BufWrapper();

    GC.Collect();

    GC.WaitForPendingFinalizers();

}

曾经怀疑GC的人,会惊奇的发现,以上代码也不存在内存问题。

这个方案在不改动现有类(未实现释放资源的IDisposable接口)的情况下,解决了内存问题,但它也存在一些的问题,如果有机会,在以后再和大家详细讨论。

 

注:对于技术问题,我喜欢提供详细方案,但不愿意对细节做太多解释,只希望有需要的人能以此为基础自行比较研究,从而获取更多

十年磨一剑,五年磨一半