30天网站建设实录视频云盘,wordpress类开源网站,短网址api,文化传媒公司网站建设前几天学习了CLR垃圾收集原理和基本算法#xff0c;但是那些是仅仅相对于托管堆而言的#xff0c;任何非托管资源的类型#xff0c;例如文件、网络资源等#xff0c;都必须支持一种称为终止化#xff08;finalization#xff09;的操作。
终止化
终止化操作允许一种资源… 前几天学习了CLR垃圾收集原理和基本算法但是那些是仅仅相对于托管堆而言的任何非托管资源的类型例如文件、网络资源等都必须支持一种称为终止化finalization的操作。
终止化
终止化操作允许一种资源在他所占的内存被回收之前首先执行一些清理工作。要提供终止化操作操作必须为类型实现一个名为Finalize的方法。当垃圾收集器判定一个对象为可收集的垃圾时它便会调用该对象的Finalize方法如果存在的话。 C#为定义Finalize方法提供了特殊的语法看下面代码
public class OSHandler { private IntPtr handler; public OSHandler(IntPtr handler) { handler handler; } // 当垃圾收集器执行时该析构函数将被调用它将关闭非托管资源句柄 ~ OSHandler() { CloseHandler(handler); } public IntPtr ToHandler() { return handler; } // 释放非托管资源 [System.Runtime.InteropServices.DllImport( Kernel32 )] private extern static bool CloseHandler(IntPtr handler); }
查看中间语言
.method family hidebysig virtual instance void Finalize() cil managed { // 代码大小 26 (0x1a) .maxstack 1 . try { IL_0000: nop IL_0001: ldarg. 0 IL_0002: ldfld native int FinalizeStudy.OSHandler:: handler IL_0007: call bool FinalizeStudy.OSHandler::CloseHandler(native int ) IL_000c: pop IL_000d: nop IL_000e: leave.s IL_0018 } // end .try finally { IL_0010: ldarg. 0 IL_0011: call instance void [mscorlib]System.Object::Finalize() IL_0016: nop IL_0017: endfinally } // end handler IL_0018: nop IL_0019: ret } // end of method OSHandler::Finalize 会发现析构函数被编译器编译为Finalize函数并且使用了异常处理。 这样当未来某个时刻垃圾收集器判定对象为可收集的垃圾时它会看到该类型定义有一个Finalize方法于是它便会调用该方法从而允许CLoseHandler函数来关闭其中的非托管资源。在Finalize方法返回之后的某个时刻该OSHandler对象在托管堆中所占的内存才会被回收。 应该避免使用Finalize方法。有以下原因
实现了Finalize的对象其代龄会被提高增加内存的压力甚至被该对象直接或者间接引用的对象的代龄也将被提升以后学习代龄。终止化对象的分配花费的时间较长因为指向它们的指针必须被放在终止化链表上;强制垃圾收集器执行Finalize方法会极大的损失程序的性能不能控制Finalize方法何时执行。对象可能会一直占有着资源直到出现垃圾收集CLR不对Finalize方法的执行顺序做任何的保障。加入对象包含指向另一个对象的指针两个对象都可能会被垃圾收集顺序的不一样会导致结果不可预期。靠个人感觉这就是一个bug。
终止化操作的内部机理 创建一个新对象new先为对象在托管堆上面分配内存。如果对象的类型定义了FInalize方法那么在该类型的实例被调用之前指向该对象的一个指针将被放到一个称为终止化链表finalization list的数据结构里面。终止化链表是一个由垃圾收集器控制的内部数据结构。链表上的每一个条目都引用着一个对象。这实际告诉垃圾收集器在回收这些对象的内存之前要首先调用它们的Finalize方法。 当垃圾收集检测到可收集的垃圾时垃圾收集器会扫描终止化链表是否有执行可收集垃圾的对象当找到这样的指针它们会从终止化链表移除并添加到一个称为终止化可达列表freachable queue的数据结构上。在终止化可达列表上出现的对象表示该对象的Finalize方法即将被调用当垃圾收集完毕后没有Finalize的对象的内存将被回收实现了Finalize的对象内存却不能被回收因为他们的Finalize方法还没有被调用。CLR有一个特殊的高优先级的线程用来专门调用Finalize方法。该线程可以避免线程同步问题。 非常有意义的是当垃圾收集器将一个对象从终止化链表转移到终止化可达队列时该对象不再认为是可收集的垃圾对象它的内存也就不可能被回收。到此为止垃圾收集器完成了垃圾对象的鉴别工作一些原先认为是垃圾的对象现在被认为不是垃圾从某种意义上来说对象又“复苏”了。当第一次垃圾收集执行完毕后特殊的CLR线程将会清空终止化可达队列中的对象同时执行其中某个对象的Finalize方法。 等下一次垃圾收集执行的时候它会看到这些终止化对象已经成为真正的垃圾对象这样实现了Finalize的对象的内存才被完全回收。 实际上终止化对象需要执行两次垃圾收集才能释放它所占用的内存。实际上由于代龄的提高可能收集次数会多于两次。上面这些玩意在Effective C#里面也讲过以前没有看懂。 Dispose模式 感觉CLR的终止化是个吃力不讨好的玩意
分配起来慢加入终止化链表收集起来更慢先是加入可达终止化列表让对象复活二次垃圾回收才能收集不能人为的控制长时间占用内存增加对象的代龄更是不可饶恕。怎么办微软总是NB的作者总是掉人胃口的CLR提供了显式释放或者关闭对象的能力但是类型需要实现一种被称为Dispose的模式当然有一些约定。如果一个类型实现了Dispose模式使用该类型的开发人员将能够知道当对象不再被使用时如何显式地释放掉它所占用的资源。 新版本的OSHandler实现应用了Dispose接口
public class OSHandler:IDisposable { private IntPtr handler; public OSHandler(IntPtr handler) { this .handler handler; } // 当垃圾收集器执行时该析构函数将被调用它将关闭非托管资源句柄 ~ OSHandler() { Dispose( false ); } public IntPtr ToHandler() { return handler; } // 释放非托管资源 [System.Runtime.InteropServices.DllImport( Kernel32 )] private extern static bool CloseHandler(IntPtr handler); public void Dispose() { // 因为对象的资源被显示清理所以在这里阻止垃圾收集器调用Finalize方法 GC.SuppressFinalize( this ); // 进行实际清理工作 Dispose( true ); } // 可以替换Dispose方法 public void Close() { Dispose(); } // 执行清理工作,protected为了子类 protected void Dispose( bool disposing) { // 线程安全 lock ( this ) { if (disposing) { // 对象正在被被显式关闭此时可以引用其他对象因为Finalize方法还没有被执行 } } if (IsValid) { // 如果handler有效那么关闭之 CloseHandler(handler); handler InvalidHandler; // 置为无效防止多次调用 } } // 返回一个无效的句柄值 public IntPtr InvalidHandler{ get { return IntPtr.Zero;}} // 判断句柄是否有效 public bool IsValid { get { return handler ! InvalidHandler; } } } 调用上面的Dispose或者Close方法只是显式释放非托管资源并不会释放托管堆中占用的内存释放对象内存的工作仍然由垃圾收集器负责当然释放时间仍然是不确定的。 上面的代码中Finalize中Dispose方法的disposing参数被设为fasle。这将告诉Dispose方法不应该执行任何其他对象的代码。在Close和无参Dispose方法中disposing参数为true因为是手动执行程序逻辑可以控制可以在if中执行代码。调用SuppressFinalize主要是为了避免终止化对象给垃圾收集器带来负担。 既然已经有了手动关闭的方法为什么还要实现Finalize方法呢因为我们不能保证程序的使用者一定会调用Dispose方法或者Close方法如果不调用将会造成资源浪费甚至系统崩溃但是这不是使用者的错误我们的程序应该考虑到这一点实现Finalize就是为了防止这种情况出现作为一个后备吧。