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