听黄老大的.Net课还是比较喜欢听他讲.Net的机制,这段时间刚好讲到.Net的类型其中当然也讲到了栈、堆和GC。Net的GC是可以隐式地执行所有必要的内存管理任务。比如回收new出来的内存空间。当然这里的回收是指对纯托管的Net资源而言比如你用Net类库里的类写的线程、数据连接之类的东西。很多情况下,我们会不知不觉的使用非Net资源比如:文件、窗口或网络连接。虽然GC可以跟踪封装非托管资源的对象的生存期,但它不了解具体如何清理这些资源。如果要清理这些资源就必须重写析构函数或者Object.Finalize 方法(在“垃圾回收”回收 Object 之前尝试释放资源并执行其他清理操作).
GC使用名为“终止队列”的内部结构跟踪具有 Finalize 方法的对象。当创建具有 Finalize 方法的对象时,GC都在终止队列中放置一个指向该对象的项。托管堆中所有需要在垃圾回收器回收其内存之前调用它们的终止代码的对象都在终止队列中含有项。如下图所表示objB就是重写了Finalize方法或析构函数的对象,而objA只是普通的纯Net对象:
当GC执行回收时,它只回收没有终结器且在栈中已经不再被引用的对象的内存。但不能回收具有终结器的且在栈中不再被引用的对象的内存。而实现过 Finalize 方法的对象要被回收需要至少两次垃圾回收才能真正在托管堆中清除,释放内存资源.所以说实现 Finalize 方法或析构函数对性能可能会有负面影响,因此应避免不必要地使用它们。
当然如果在程序中使用了非托管资源的时候,就可以放心的大胆的去使用Finalize 方法或析构函数,因为不去释放资源的代价比冒险释放的代价更大,这里用冒险来形容似乎不太准确,但如果不注意方法确实是比较冒险的. 例如公共语言运行库关闭这种情况.
MS为程序开发者提供了IDisposable 接口来实现释放非托管资源和托管资源,该接口只有一个Dispose()方法.按照下面的这个模式来设计就没有错误了.
// 基类设计模式. public class Base: IDisposable { //实现 IDisposable 接口必须实现Dispose(). public void Dispose() { Dispose(true); GC.SuppressFinalize(this); //请求系统不要调用指定对象的终结器 } protected virtual void Dispose(bool disposing) { if (disposing) { // 在这里释放托管资源. } // 在这里释放非托管资源. // 将大对象字段在这里设置为null. } // Finalize 方法或析构函数. ~Base() { Dispose (false); } } // 继承子类的设计模式. public class Derived: Base { protected override void Dispose(bool disposing) { if (disposing) { // 在这里释放托管资源. } // 在这里释放非托管资源. // 将大对象字段在这里设置为null. // Call Dispose base class. base.Dispose(disposing); } // 其中子类并不需要提供向父类 // 一样的public Dispose()方法 // 会从父类继承下来 } |