小心编写终结器代码
.NET语言里对象的终结器与常见面向对象语言中对象的释构器虽然可以类比,却有着很大区别,它所具有的某些特点有时候会给我们的代码带来较大的灵活性,但也有时候会成为一个害人的陷阱。因此我们在不得不使用终结器的时候必须小心去编写终结器代码。
1.终结器只在GC进行垃圾回收时被调用,通常难以在程序中确定它的调用时机。
几乎所有介绍终结器的资料里都会着重指明这点,这也是通常不推荐终结器里编写清理代码的最大原因,引入Dispose模式是常见的替代方法,在.NET类库中随处可见这样的范例。
2.终结器不可以在ValueType上使用。
所有ValueType类型的对象被回收时都不会执行终结器代码,比如在一个struct里定义了终结器(在某些.NET语言里这是合法的),则该终结器将不会被GC执行。
3.终结器被调用后对象并没有被收回,而会在下一次垃圾回收过程中才可能会被真正释放。
GC启动垃圾回收过程后会扫描当时所有被有效引用的对象即“可访问”的对象,标记并移动这些对象,这样剩下的那些对象即是所谓"垃圾"对象,如果没有终结器机制,GC只要直接释放这些内存就可以了,但实际上并不是这样,GC维护了一个"所有包含终结器对象的列表",在确认"垃圾"对象之后将那些包含终结器的"垃圾"对象从列表中移除并放到一个"准备执行终结器"的队列中,这才释放那些不包含终结器的"垃圾"对象。这样,而那些包含终结器的对象在该轮清理中只执行了终结器代码,只有在下一次垃圾回收过程中才会真正去释放它们。
设想这样的情况,如果我们废弃的对象使用了大量内存(比如包含了一个DataSet),并且它定义了一个终结器,那么至少要经过2轮垃圾回收过程它所占用的内存才可能被释放,在某些场合下这对性能的影响是巨大的。
4.终结器可以被挂起。
调用GC.SuppressFinalize(object obj)可以取消一个对象的终结器。
为什么存在这个特征?我想它应该是为了解决上文第三点所谈到的情况,看看这样一段代码
{
protected virtual void Dispose(bool disposing)
{
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this); //取消终结器
}
~DisposableObject()
{
this.Dispose(false);
}
}
但这并不是完美的,DisposableObject要求该类的继承者们清楚的了解其父类的实现,如果DisposableObject的某个子类如此实现
{
~MyObject()
{
DestroyNativeMemories();
}
}
而MyObject类的某些使用者,如此使用这个类
obj.DoSth();
}
这样他们将得到内存泄露。注意.NET类库里有很多类与该DisposableObject实现相似,比如System.Windows.Forms.Form。