21.3 使用需要特殊清理的类型
多数类型有内存就能正常工作,有的类型除了内存还需要本机资源,所以需要特殊清理。
CLR提供了终结(finalization)的机制,允许对象在判定为垃圾之后,但在对象内存回收之前执行一些代码。
终极基类System.Object定义了受保护的虚方法Finalize。垃圾回收器在判定对象是垃圾之后,会调用对象的Finalize方法(如果重写)。
特点
- 被视为垃圾的对象在垃圾回收完毕之后才调用Finalize方法,因为Finalize方法可能要执行访问执行字段的代码,可终结对象在回收时必须存活,造成它被提升一代,使对象活的时间比较长
- Finalize执行时间控制不了,只有在GC之后才运行Finalize,CLR不保证多个Finalize方法的调用顺序,所以在
Finalize
方法中不要访问定义了Finalize方法的其他类型的对象,那些对象可能已经终结了 - CLR用一个特殊的、高优先级的专用线程调用FInalize方法来避免死锁
21.3.1使用包装了本机资源的类型
自定义包装了本机资源的SafeHandle派生类
类如果想允许使用者控制类所包装的本机资源的生存期,就必须实现IDisposable接口
调用Dispose来释放对象自身使用的资源
如果决定显示调用Dispose,建议将调用放到异常处理的finally块中,确保代码被执行
21.3.2一个有趣的依赖性问题
FileStream类型只支持字节的写入。写入字符和字符串可以使用StreamWriter
FileStream fs = new FileStream("Datafile.dat", FileMode.Create);
StreamWriter sw = new StreamWriter(fs);
sw.Write("Hi there");
sw.Dispose();
向一个StreamWriter对象写入时,它会将数据缓存在自己的内存缓冲区,缓冲区满时,StreamWriter对象将数据写入Stream对象。StreamWriter类型不支持终结,所以通过StreamWriter对象写入数据完毕后应显示调用Dispose,否则会造成数据丢失
21.3.3GC为本机资源提供的其他功能
GC提供两个静态方法AddMemoryPressure 和RemoveMemoryPressure
如果一个类要包装很大的本机资源,就应该使用这些方法提示垃圾回收器实际需要消耗多少内存,垃圾回收器会监视内部压力变化,压力变大时强制执行垃圾回收
如果一个类要包装数量有限制的本机资源,使用HandleCollector的实例来提示垃圾回收器要使用资源的多少个实例。
21.3.4 终结的内部构造原理
- 应用程序创建对象时,如果对象的类型定义了Finalize方法,那么在该类型的实例构造器被调用之前,会将指向该对象的指针放到终结列表中。
- 垃圾回收开始时,对象会被判定为垃圾。垃圾回收器扫描终结列表,查找对垃圾对象的引用,找到一个引用,会将该引用从终结列表中移除,并附加到freachable队列。
- 一个特殊的高优先级的CLR线程专门调用Finalize方法,freachable队列中一旦有记录项出现,线程就被唤醒,将每一项从队列中移除并调用每个对象的Finalize方法。
当垃圾回收器将对象的引用从终结列表移至freachable队列时,我们称对象复活了,标记freachable对象时,递归标记对象中的引用类型的字段所引用的对象,所有这些对象必须复活以便在回收过程中存活。之后,垃圾回收器结束标记,移动可回收内存,将复活的对象提升至较老的一代。所以,可终结对象需要执行两次垃圾回收才能释放它占用的内存。
21.3.5手动监视和控制对象的生存期
GCHandleType枚举类型有四个标志
Weak:该标志允许监视对象的生存期。可检测垃圾回收器在什么时候判定该对象在应用程序代码中不可达。此时,对象的Finalize方法(如果有的话)可能已经执行,也可能未执行
WeakTrackResurrection:同Weak,但此时对象的Finalize方法(如果有的话)已经执行,对象的内存已被回收
Normal:该标志允许控制对象的生存周期。是告诉垃圾回收器,即使应用程序中没有根引用该对象,该对象也必须留在内存中。垃圾回收时,该对象的内存可以压缩。
Pinned:同Normal,垃圾回收时,该对象的内存不能压缩。
垃圾回收时,垃圾回收器的动作
- 垃圾回收器标记所有可达的对象,然后垃圾回收器扫描GC句柄表,所有Normal和Pinned对象被看成是根,同时递归标记这些对象字段引用的对象。
- 垃圾回收器扫描GC句柄表,查找所有Weak记录项,如果一个Weak记录项引用未标记的对象,该引用标识就是不可达对象(垃圾),该记录项的引用值更改为null
- 垃圾回收器扫描终结列表。在列表中,对未标记对象的引用标识的是不可达对象,这些引用移至freachable队列,这是对象会被标记,又变成可达了。
- 垃圾回收器扫描GC句柄表,查找所有WeakTrackResurrection记录项。如果一个WeakTrackResurrection记录项引用了未标记的对象,该引用标识的就是不可达对象,该记录项的引用值更改为null。
- 垃圾回收器对内存进行压缩。Pinned对象不会压缩,垃圾回收器会移动它周围的其他对象