C#垃圾回收

读完下面的文章会有种感觉,只有类型含有非托管资源时,才需要考虑手动清理.其实不然,如果类型中只有托管资源,实现IDispose可以实现定时的资源清理

可以准确的知道资源在何时被清理.

只用终结器(虚构函数)的方法(不与IDispose一起使用),对性能有负面影响,内存清理将会推迟到第三代的GC,资源驻留内存中的时间会加长.

 

所以,个人认为,对于C#中的资源管理,仅当程序集中使用了非托管资源时,需要使用Dispose模式(实现析构函数)进行高效的管理资源.当需要定时或频繁的资源清理时,采用继承

IDispose方式(不实现析构函数)来清理资源.

 

关于几次垃圾回收时机的测试:

http://www.haogongju.net/art/1017342

 

另一篇文章对资源的描述:

需要明确一下C#程序(或者说.NET)中的资源。简单的说来,C#中的每一个类型都代表一种资源,而资源又分为两类:

托管资源:由CLR管理分配和释放的资源,即由CLR里new出来的对象;

非托管资源:不受CLR管理的对象,windows内核对象,如文件、数据库连接、套接字、COM对象等;

毫无例外地,如果我们的类型使用到了非托管资源,或者需要显式释放的托管资源,那么,就需要让类型继承接口IDisposable。这相当于是告诉调用者,该类型是需要显式释放资源的,你需要调用我的Dispose方法。

那么,为什么要区别对待托管资源和非托管资源。在认真阐述这个问题之前,我们需要首先弄明白:托管资源需要手动清理吗?不妨先将C#中的类型分为两类,一类继承了IDisposable接口,一类则没有继承。前者,我们暂时称之为非普通类型,后者我们称之为普通类型。非普通类型因为包含非托管资源,所以它需要继承IDisposable接口,但是,这个包含非托管资源的类型本身,它是一个托管资源。所以说,托管资源需要手动清理吗?这个问题的答案是:托管资源中的普通类型,不需要手动清理,而非普通类型,是需要手动清理的(即调用Dispose方法)。

(注:也不尽然.实现IDispose只是提供一种回收机制,完全可以不实现而只在析构函数中处理需要处理的非托管资源,只是性能有所损失而已.所以,这样说来,就是托管资源不需要手动清理,非托管资源一定要注意去手动清理.)

Dispose模式设计的思路基于:如果调用者显式调用了Dispose方法,那么类型就该按部就班为自己的所以资源全部释放掉。如果调用者忘记调用Dispose方法,那么类型就假定自己的所有托管资源(哪怕是那些上段中阐述的非普通类型)全部交给垃圾回收器去回收,而不进行手工清理。理解了这一点,我们就理解了为什么Dispose方法中,虚方法传入的参数是true,而终结器中,虚方法传入的参数是false。

 

引用正文:

这两天帮助其它项目组Review代码,发现有些地方实现了IDispose接口,同时也发现了一些关于IDispose的问题:

1.A类型实现了IDispose接口,B类型里面含有A类型的字段,B类型没有实现IDispose接口

2.一个类里面实现了Finalize终结器,同时也实现了IDispose接口,但在Dispose方法里面没有调用GC.SuppressFinalize(this)方法.

下面我对以上两个问题分别分析一下,并提出解决方案。

问题1

如果A类型里面有非托管资源需要在实现的IDispose接口里面释放,由于B类型没有实现IDispose接口,B类型的使用者要想释放A类型的非托管资源并不方便.这样的话,就有可能忘记了释放A类型的非托管资源.

解决方案:

实现B类型的IDispose接口,在Dispose方法里面调用A类型的Dispose方法.这样,B类型的使用者在调用B类型Dispose的同时,就把A类型的Dispose也调用了.

问题2

在Dispose方法里面没有调用GC.SuppressFinalize(this)方法,会有什么问题呢,这样会导致垃圾回收器不能对 这个类型的对象及时回收. 当GC开始工作的时候,它首先将没有终结器的垃圾对象从内存中移除,有终结器的所有对象则添加到一个垃圾队列当中。GC会调用一个新线程来执行这些对象的 终结器。当终结器执行完毕后,这个对象会从队列中被移除。这个对象在队列中移除之后,当GC再次开始工作的时候,这个对象才能够被回收,所以有终结器的对 象会比没有的在内存中保留更长的时间。在后面我会对这里再详细的描述一下.

解决方案:

在Dispose方法中调用GC.SuppressFinalize(this)方法.这样的话,就不会把有终结器的对象则添加到垃圾队列当中.

切入正题

.net中,非托管代码清理有两种方式:Finalize方式和Dispose方式.

Finalize方式:通过对自定义类型实现一个Finalize方法来释放非通过资源.

从.net2.0开始,C#编译器不能对Finalize进行显示的调用和重写,必须使用析构函数来实现它.

 

class A
{
~A()
{
释放资源;
}
}

上面的代码就是通过Finalize方式来释放资源的跟C++用析构函数释放资源的代码很象.

但是它实现方式和C++不同,因为它是由垃圾回收器来管理内存的.

大家看到了,用Finalize方式释放非托管资源很简单,但是如果你了解了他的实现方式,你可能就不会选择用它来释放非托管资源.

那Finalize方式在.net内部是如何实现的呢?

当GC(垃圾回收器)开始工作的时候,它首先将没有终结器的垃圾对象从内存中移除,有终结器的所有对象则添加到一个终止化队列当中。GC会调用一个 新线程来执行这些对象的终结器。当终结器执行完毕后,这些对象会从队列中被移除。这时候由于这些对象在第一次检测到的时候没有被释放,它们将会进入第1代 对象,直到GC检测到第0代对象和第1代对象再次充满时,这时候GC才会把刚才那些对象释放掉,所以有终结器的对象会比没有的在内存中保留更长的时间。

提示:垃圾回收器把托管堆中的对象分为3代,分别是0,1,2.一般分配为:0代约256K,1代约是2MB,第2代约是MB,代龄越高,容量就越 大,显然效率也就越低.首先被添加到托管堆中的对象被定为第0代,当第0代充满时,就会执行垃圾回收,未被回收的对象代领将提升1代.

由于以上原因应该避免仅使用Finalize方式释放非托管资源.

Dispose模式:在自定义类中实现IDispose接口,在接口中的Dispose方法中对非托管资源进行释放.闲话少说,上代码

 

public class MyResourceRelease: IDisposable
{
/// 保证资源只用释放一次
private bool _alreadyDisposed = false;
/// 用来判断释放资源的类别(托管和非托管)
protected virtual void Dispose(bool isDisposing)
{
if(_alreadyDisposed)
{
return;
}
if(isDisposing)
{
//释放托管资源
}
//释放非托管资源
_alreadyDisposed = true;
}
public void Dispose()
{
Dispose(true);
}
}

上面的代码就是用Dispose方式释放资源的方法.因为上面自定义的Dispose(bool isDisposing)方法是virtual的,所以还可以在派生类里面对它进行override

 

public class MyDerivedResource: MyResourceRelease
{
private bool _disposed = false;
protected override void Dispose(bool isDisposing)
{
if(_disposed)
{
return;
}
try
{
if(isDisposing)
{
//释放托管资源
}
//释放非托管资源
_disposed = true;
}
finally
{
base.Dispose(isDisposing);
}
}
}


这样可以确保释放继承链上所有对象的引用资源,在整个继承层次中传播Dispose模式.

 

那用Dispose方式非托管资源就是最好的方法了吗?

其实不然,因为类型实现了IDispose接口,这个类的使用者必须显示调用Dispose方法,或者在创建该类型对象的时候使用using关键 字,对于一些粗心的使用者可能会忘记调用Dispose方法,或者没有使用using关键字,这样就导致了非托管资源没有释放的后果.

最佳方案

同时实现终结器和Dispose方式.这样对于细心的使用者直接显示调用Dispose方法会提高垃圾回收的性能,对于粗心的使用者虽然忘记了调用Dispose方法,但也不至于使得非托管资源得不到释放.

注意这里用到了GC. SuppressFinalize(this)方法.

代码如下:

 

public class MyResourceRelease: IDisposable
{
~MyResourceRelease()
{
Dispose(false);
}
/// 保证资源只用释放一次
private bool _alreadyDisposed = false;
/// 用来判断释放资源的类别(托管和非托管)
protected virtual void Dispose(bool isDisposing)
{
if(_alreadyDisposed)
{
return;
}
if(isDisposing)
{
//释放托管资源
}
//释放非托管资源
_alreadyDisposed = true;
}
public void Dispose()
{
Dispose(true);
//阻止GC把该对象放入终结器队列
GC.SuppressFinalize(this);
}
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值