C#垃圾回收和资源管理

垃圾回收

什么是垃圾回收

垃圾回收是运行时的一个功能,作用是回收不再被引用的对象所占用的内存。垃圾回收器只负责回收内存,不处理其他资源,比如数据库连接、句柄(文件、窗口等)、网络端口及硬件设备等。这样意味着,假如维持对一个对象的引用,就会阻止垃圾回收器重用对象使用的内存。

.net中的垃圾回收原理

在.net中,垃圾回收器才用的是mark-and-compact算法,在一次垃圾回收周期开始的时候,它要识别对象的所有根引用,根引用是来自静态变量、CPU寄存器以及局部变量或者参数出所有可达对象,

在执行垃圾回收的时候,垃圾回收器不是每句所有访问不到的对象;相反,它是通过压缩所有相邻的可达对象来执行垃圾回收。这样一来,由不可访问的对象(也就是垃圾)占用的任何内存就会被覆盖。

为了尽量避免在不恰当的时间执行垃圾回收,System.GC对象包含了一个Collect()方法。你可以在执行一些关键代码之前调用(在这些代码执行的时候,不希望GC运行)。这样会显著减小垃圾回收器运行的可能性。

.net垃圾回收一个特别之处在于,并非所有垃圾都一定能在一个垃圾回收周期中清楚。由于相较于长期存在的对象,最近创建的对象更有可能需要进行垃圾回收。为了强调这个行为,.net垃圾回收器支持“代”(generation)的概念,它会以更快的频率尝试清楚生存时间较短的对象。而那些已经在一次垃圾回收中“存活”下来的对象(老对象)会以较低的频率清楚。具体的说,总共3代对象。一个对象每次在一个垃圾回收周期中存货下来,它都会移动到下一代,直到最终移动到第二代(从第零代开始)。相较于第二代对象,垃圾回收器会以更快的频率对第零代的对象执行垃圾回收。

弱引用(较少使用)


资源管理

在上一段中提到,垃圾回收的宗旨是提高内存利用率,而并不是用来清理文件句柄、数据库连接、端口或者其他有限资源的。那么在.net中,我们如何对这些资源进行管理。

Finalizer(终结器、完成器)

语法类似与c++中的析构函数,例

public Test
{
    ~Test()
    {
        //TODO
    }
}

finalizer允许程序员编写代码来清理一个类的资源,但是不能显示的调用,因此,开发者不能再编译时确定执行finalizer的时机。唯一确定的就是finalizer会在上一次使用对象之后,并在应用程序关闭之前的某个时间由垃圾回收器调用运行。finalizer只在进程自然结束之前执行。假如进程意外终止,例如,计算机关机或者进程被强行终止,就会阻止finalizer的运行。

注意,由于finalizer不能显示的执行,并且会在它们自己的线程中执行,这使得执行的时机变得非常不确定,这种不确定使finalizer中的一个未处理异常变得难以诊断。有鉴于此,一定要避免在finalizer中出现异常。


使用using语句进行确定性终结

finalizer的问题在于它们不支持确定性终结,finalizer只是作为对资源进行清理的一个备用机制来使用的,假如程序员忘记显示调用必要的清理代码,就可以依赖finalizer来清理资源。例如,一个TemporaryFileStream类不仅包含一个finalizer,还包含一个Close()方法。该类要求使用一个可能大量占用磁盘空间的文件资源。使用TemporaryFileStream的开发者可以显示调用Close()来回收磁盘空间。在finalizer中调用Close()方法,可以防止开发者忘记显示调用Close回收资源,但资源回收的时间并不确定。

为了提供确定性终结的方法,c#提供了一个接口IDisposable,该接口提供一个Dispose()的方法来定义这个模式的细节。开发者可以针对一个资源类来调用该方法,从而“释放(dispose)”当前消耗的资源。例:

	public class TemporaryFileStream : IDisposable
	{
		private FileStream _fileStream;

		~TemporaryFileStream()
		{
			Close();
		}

		public TemporaryFileStream(string fileName)
		{
			_fileStream = new FileStream(fileName, FileMode.OpenOrCreate);
		}

		public void Close()
		{
			if (_fileStream != null)
			{
				_fileStream.Close();
			}
			GC.SuppressFinalize(this);
		}

		#region IDisposable Members

		public void Dispose()
		{
			Close();
		}

		#endregion
	}

但是,有可能在实例化TemporaryFileStream之后并在调用Dispose之前发生异常,这样Dispose就不会被执行,对资源的清理仍旧又依赖于finalizer。为了避免这个问题,调用者需要实现一个try/finally块,在finally中调用dispose以保证dispose一定会被执行。.net针对这一情况提供了一个更便捷的写法就是using,例

			using(TemporaryFileStream s = new TemporaryFileStream())
			{ 
			}

最终生成的CIL代码与显式的try/finally块是一样的。

IDisposable模式包含另外一个重要的调用。看之前的代码,可以发现在Close方法里面有一句代码

GC.SuppressFinalize(this);
这句话的作用是从终结队列中移除TemporaryFileStream类的实现。清晰的言之, 调用 SuppressFinalize(object) 之后,finalizer将不会被回收器调用。因此,通常将此句代码写在Dispose方法末尾,告知回收器,资源已经被显式的调用Dispose回收,不必再调用finalizer。


代码中资源管理的指导原则

  • 只有在对象使用了稀缺或者昂贵的资源的前提下,才实现finalizer。finalizer会推迟垃圾回收
  • 有finalizer的对象应该实现IDisposable来支持确定性终结
  • finalizer通常调用与IDisposable调用相同的代码,可能就是调用Dispose()方法
  • finalizer应避免造成任何未处理的异常
  • 像Dispose()与Close()这样的确定性终结方法应该调用System.GC.SuppressFinalize()。使垃圾回收更快的发生,并避免重复进行资源清理。
  • 负责资源清理的代码应该允许多次调用
  • 资源清理的方法应该足够简单,并且只着重于清理由终结实例引用的资源。不应该引用其他对象。
  • 若基类实现了Dispose(),则派生类的实现需要调用基类的实现
  • 代码应该确保在调用了Dispose()之后,对象不能被继续使用。


Dispose模式的实现

实现IDisposable的Dispose方法,继承基类的Dispose(bool)方法,Dispose调用带参数的Dispose(true),finalizer调用Dispose(false)

当参数为true时,同时释放托管及非托管资源,当参数为flase时,只释放非托管资源。例:

  public class SampleClass : IDisposable
  {
   //演示创建一个非托管资源
   private IntPtr nativeResource = Marshal.AllocHGlobal(100);


    //演示创建一个托管资源
    private AnotherResource managedResource = new AnotherResource();

    private bool disposed = false;

  ///
  /// 实现IDisposable中的Dispose方法
  ///
  public void Dispose()
  {
  //必须为true
  Dispose(true);

  //通知垃圾回收机制不再调用终结器(析构器)
  GC.SuppressFinalize(this);
  }

  ///
  /// 不是必要的,提供一个Close方法仅仅是为了更符合其他语言(如C++)的规范
  ///
  public void Close()
  {
  Dispose();
  }

  ///
  /// 必须,以备程序员忘记了显式调用Dispose方法
  ///
  ~SampleClass()
  {
  //必须为false
  Dispose(false);
  }

  ///
  /// 非密封类修饰用protected virtual
  /// 密封类修饰用private
  ///
  ///
  protected virtual void Dispose(bool disposing)
  {
  if (disposed)
  {
  return;
  }
   if (disposing)
  {
  // 清理托管资源
  if (managedResource != null)
  {
  managedResource.Dispose();
  managedResource = null;
  }
  }

  // 清理非托管资源
  if (nativeResource != IntPtr.Zero)
  {
  Marshal.FreeHGlobal(nativeResource);
  nativeResource = IntPtr.Zero;
  }

  //让类型知道自己已经被释放
  disposed = true;
  }

  public void SamplePublicMethod()
  {
  if (disposed)
  {
  throw new ObjectDisposedException(SampleClass, SampleClass is disposed);
  }
  //省略
  }
  }


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值