new新对象时,如果该对象的类型定义了Finalize方法,那么在该类型的实例构造器被调用之前,会将指向该对象的指针放到一个终结列表中,该列表由GC内部控制。
当可终结对象被回收时,会将引用从终结列表移动到freachable队列中,该队列由GC内部控制。
CLR会启用一个特殊的高优先级线程来专门调用Finalze方法。freachable队列为空时,该线程将睡眠;但一旦队列中有记录项出现,线程就会被唤醒,将每一项都从freachable队列中移除,并调用每个对象的Finalize方法。
如果类型的Finalize方法是从System.Object继承的,CLR就不认为该对象是“可终结”的,只有当类型重写了Object的Finalize方法时,才会将类型及其派生类型的对象视为“可终结”的。
注意,除非有必要,否则应尽量避免定义终结器。原因如下:
可终结对象在回收时,必须保证存活,这就可能导致其被提升为另一代,生存期延长,导致内存无法及时回收。另外,其内部引用的所有对象也必须保证都存活,一些被认为是垃圾的对象在可终结对象回收后也无法直接回收,直到下一次(甚至多次)GC时才会被回收。
Finalize 方法在GC完成后才会执行,而GC的执行时机无法控制,也就导致该方法的执行时间也无法控制。
Finalize 方法中不要访问其他可终结对象,因为CLR无法保证多个 Finalize 方法的执行顺序。如果访问了已终结的对象,Finalize 方法抛出未处理的异常,导致进程终止,无法捕捉异常。
在实际项目开发中,想要避免释放本机资源基本不可能,但是我们可以通过规范代码来规避异常,这就需要用到IDisposable接口了。示例代码如下:
public class MyResourceHog : IDisposable { //标识资源是否已被释放 private bool _hasDisposed = false; public void Dispose() { Dispose(true); //阻止GC调用 Finalize GC.SuppressFinalize(this); } /// <summary> /// 如果类本身包含非托管资源,才需要实现 Finalize /// </summary> ~MyResourceHog() { Dispose(false); } protected virtual void Dispose(bool isDisposing) { if (_hasDisposed) return; //表明由 Dispose 调用 if (isDisposing) { //释放托管资源 } //释放非托管资源。无论 Dispose 还是 Finalize 调用,都应该释放非托管资源 _hasDisposed = true; } } public class DerivedResourceHog : MyResourceHog { //基类与继承类应该使用各自的标识,防止子类设置为true时无法执行基类 private bool _hasDisposed = false; protected override void Dispose(bool isDisposing) { if (_hasDisposed) return; if (isDisposing) { //释放托管资源 } //释放非托管资源 base.Dispose(isDisposing); _hasDisposed = true; } }