析构方法的调用时机和GC一样是完全不可预测的,也不应当依赖于它被调用。甚至于它也不一定会被调用,和GC一样,不一定会发生。
析构函数是确保已分配的非托管资源总能被释放的一个补救措施。如果可能就不应当被调用,譬如说手动释放了非托管资源,此时应当通知GC取消对对象的析构函数的调用。
所以:
首先托管资源足够好用也够用,一般情况下用不到非托管资源。
其次非托管资源有丰富的安全的类库封装,一般情况下不需要自己分配。
最后,如果你一定要手动分配非托管资源,那么记住析构函数是保险丝,是最后的保障,不是常规的做法。
1.C#中的finalization是类内的一个实例方法,当这个类型的实例不可达时会被调用。
2.不可达的含义是,在进行GC的标记阶段,该对象被认为是没有被任何根引用的对象。
3.GC必须知道所有的可终结对象,当它们不可达时调用它们的 finalizers。GC把这类对象记录在 finalization queue。换言之,finalization queue 在任何时刻包含着所有的存活的可终结对象。如果有许多对象处于 finalization queue,不意味着一些坏事会发生,仅仅表明当前存活的对象中有很多定义了 finalizer。
4.在GC过程的标记阶段完成时,GC检查 finalization queue 中有哪些对象不再可达。如有有些finalization queue 中的对象不可达,它们不会被立刻删除,因为它们的 finalizer 还未执行。因此,这些不可达的对象移动到 fReachable queue,fReachable 的意思是 finalizer reachable,虽然在标记阶段后这个对象不再被认为可达,但 finalizer 仍然可以访问它。只要 fReachable queue 中有对象,GC会指示 finalizer thread 来运行这些 finalizer 。
5.Finalizer thread 是另一种由.NET运行时创建的线程,它会一个接一个的移除 fReachable queue 中的对象,并调用其 finalizer 。 finalizer thread 运行时可能在 finalizer 的代码中分配对象,因此,在垃圾处理完成后,用户线程开始执行的时候, finalizer thread 才会开始执行。在 fReachable queue 中移除对象后,这个对象在GC看来变得不可达,会在下次对这个对象所在的代回收时回收这个对象。
6.进一步的, fReachable queue 在标记阶段视为根,因为 finalizer thread 不一定会在本次GC结束,下次GC开始前完成工作。这使得可终结对象更容易暴露在中间代危机中,一个在G0回收的可终结对象本应在G1被回收,但由于 finalizer thread 处理不及时,被升代至G2.
7.可以通过 GC.WaitForPendingFinalizers 挂起调用线程,直到 fReachable queue 清空。作为副作用,所有的 fReachable queue 变得不可达,可以在对应代GC时清理。
使用如下方式可以避免中间代危机,可以精确的清理内存。
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
这种方式首先进行 full-blocking GC 发现所有的 fReachable objects,然后让当前线程等待 fReachable queue 清空,让这些对象不可达,最后再次进行 full-blocking GC,回收 fReachable objects 的内存。
很明显,如果其他线程在这个线程调用 GC.WaitForPendingFinalizers 的时候分配了可终结对象,那么这些可终结对象会升至G1,再次调用 GC.Collect 时会升至G2, fReachable queue 可能添加新的项。这使得你无法在不暂停其他线程的情况下清空所有的内存垃圾。
8.有两个重要的 finalization API 暴露在 GC 类中:
a.GC.ReRegisterForFinalize(object),允许已经注册过的对象再次注册到 finalization queues 。这个方法所作的事情调用与分配器分配可终结对象注册在 finalization queues 使用同一个内部方法。它通常出现在 finalizer 代码中,它的开销并不重要。(用来构建对象池)
b.GC.SuppressFinalize(object),这个方法请求运行时不要调用指定对象的终结器。它广泛的使用与 Disposable 模式,被高度优化。调用这个方法不会操作 finalization queues,而是对 对象头置位。这样,这些置位的对象就不会在 finalizer thread 上调用 finalizer 。(广泛使用)
9.实现IDisposable 接口实现显式资源释放。(不展开讲)
10.使用 SafeHandle 包装句柄。(不展开讲)