默认情况下,GC自动回收的资源只有托管堆上的内存,其他资源如数据库连接、流等等都不在GC的管理范围之内,这些资源统称为非托管资源。
当不使用托管资源的时候,默认的析构函数就可以搞定一切。只有在需要手工释放非托管资源的时候才应该(不是必须)重写析构函数。
有一点和C++不一样的地方,在C#里只有类才在托管堆上分配内存,也只有类才有析构函数,struct是不能有析构函数的。所以尽量不要在struct里分配非托管资源,因为struct不得不失去析构函数这一层保障。
GC在工作的时候,会先停下该进程的所有线程。所以对于托管资源,没事的时候不要自己强制进行垃圾回收,会影响性能。只有当很特殊的情况,例如你刚刚分配了一大堆内存,而又马上要进入一个不想被垃圾回收打断的过程,这时候才值得手工垃圾回收。
手工垃圾回收的代码:
GC.Collect();
GC.WaitForPendingFinalizers(); //Suspends the current thread until the thread that is processing the queue of finalizers has emptied that queue.
有三代:0,1,2
0代最有可能被回收
1代次之
2代最不可能被回收
年龄越大的被回收的可能性越小。
当垃圾回收被某个事件触发的时候,会先清理0代,把能清理的都清理了,不能清理的挪到1代。若把0代的清理完了内存还不够用再清理1代,把1代能清理的都清理了,还有用的挪到2代。2代就到头了,不能往后挪了。
托管资源交给GC就好,非托管资源则必须亲自写代码回收。因此对于非托管资源,一般要写一个Dispose方法来实现IDisposable,这样就可以放在using块中自动调用Dispose,也可以显示调用Dispose函数来进行清理。除此之外,最好在析构函数中也释放资源,一旦忘记调用Dispose函数也好有个补救。但不能只依赖析构函数,因为GC什么时候开始进行垃圾回收还不一定,对象处于第几代也不能事先确定,所以等到GC启动了可能某些稀缺资源早就耗尽了。
下面是MSDN上的best practice,非常有参考价值:
实现IDispose:http://msdn.microsoft.com/zh-cn/library/system.idisposable.dispose.aspx
有继承关系的类的Dispose:
public class Base : IDisposable
{
private bool disposed = false ;
// Implement IDisposable.
public void Dispose()
{
Dispose( true );
GC.SuppressFinalize( this ); // Prevent finalization code executing
}
protected virtual void Dispose( bool disposing)
{
if (disposed)
{
return ;
}
if (disposing)
{
// TODO: Release managed resources.
}
// TODO: Release unmanaged resources.
}
// Use C# destructor syntax for finalization code.
~ Base()
{
// Simply call Dispose(false).
Dispose( false );
}
}
// Design pattern for a derived class.
public class Derived : Base
{
protected override void Dispose( bool disposing)
{
if (disposed)
{
return ;
}
if (disposing)
{
// TODO: Release managed resources.
}
// TODO: Release unmanaged resources.
// Call Dispose on your base class.
base .Dispose(disposing);
}
// The derived class does not have a Finalize method
// or a Dispose method without parameters because it inherits
// them from the base class.
}
上面一段代码中有一点我还不太明白,即为什么析构函数用Dispose(false),而不敢去释放托管资源,在MSDN上找到的解释是:
Dispose(bool disposing) executes in two distinct scenarios. If disposing equals true, the method has been called directly or indirectly by a user's code and managed and unmanaged resources can be disposed. If disposing equals false, the method has been called by the runtime from inside the finalizer and only unmanaged resources can be disposed. When an object is executing its finalization code, it should not reference other objects, because finalizers do not execute in any particular order. If an executing finalizer references another object that has already been finalized, the executing finalizer will fail.
它说在析构函数中引用的某些对象有可能已经被释放了,所以再去释放它们是不对的。似乎有点儿道理,但问题是,如果析构函数不被调用,那么包含在这个类中的其他对象的引用怎么可能被析构了呢?