标签:
Finalize方法非常有用,因为它确保了当托管对象的内存被释放时,本地资源不会泄漏。但是,Finalize方法的问题在于,他的调用时间不能保证。另外,由于他不是公共方法,所以类的用户不能显式调用它。
类型为了提供显式进行资源清理的能力,提供了Dispose模式。所有定义了Finalize方法的类型都应该同时实现Dispose模式,使类型的用户对资源的生存期有更多的控制。
类型通过实现System.IDisposable接口的方式来实现Dispose模式:
public interface IDisposable { void Dispose(); }
任何类型只有实现了该接口,将相当于声称自己遵循Dispose模式。无参Dispose和Close方法都应该是公共和非虚的。
FileStream类实现了System.IDisposable接口。
FileStream fs = new FileStream(); //显示关闭文件 Dispose/Close fs.Dispose(); fs.Close(); fs.Write();
显示调用一个类型的Dispose或Close方法只是为了能在一个确定的时间强迫对象执行清理。这两个方法并不能控制托管堆中的对象所占用的内存的生存期。这意味着即使一个对象已完成了清理,仍然可在它上面调用方法,但会抛出ObjectDisposedException异常。
如果决定显式地调用Dispose和Close这两个方法之一,强烈建议把它们放到一个异常处理finally块中。这样可以保证清理代码得到执行。
C#提供了一个using语句,这是一种简化的语法来获得上述效果。
using(FileStream fs = new FileStream()) { fs.Write(); }
在using语句中,我们初始化一个对象,并将它的引用保存到一个变量中。然后在using语句的大括号内访问该变量。编译这段代码时,编译器自动生成一个try块和一个finally块。在finally块中,编译器会生成代码将变量转型成一个IDispisable并调用Dispose方法。显然,using语句只能用于哪些实现了IDisposable接口的类型。
CLR为每一个AppDomain都提供了一个GC句柄表 (GC Handle table) 。该表允许应用程序监视对象的生存期,或手动控制对象的生存期。
在一个AppDomain创建之初,该句柄表是空的。句柄表中的每个记录项都包含以下两种信息:一个指针,它指向托管堆上的一个对象;一个标志(flag),它指出你想如何监视或控制对象。
为了在这个表中添加或删除记录项,应用程序要使用System.Runtime.InteropServices.GCHandle类型。
前面说过,需要终结的一个对象被认为死亡时,垃圾回收器会强制该对象重生,使它的Finalize方法得以调用。Finalize方法调用之后,对象才真正的死亡。
需要终结的一个对象会经历死亡、重生、再死亡的“三部曲”。一个死亡的对象重生的过程称为复活(resurrection) 。复活一般不是一件好事,应避免写代码来利用CLR这个“功能”。
前面讨论的垃圾回收算法有一个很大的前提就是:只在一个线程运行。
在现实开发中,经常会出现多个线程同时访问托管堆的情况,或至少会有多个线程同时操作堆中的对象。一个线程引发垃圾回收时,其它线程绝对不能访问任何线程,因为垃圾回收器可能移动这些对象,更改它们的内存位置。
CLR想要进行垃圾回收时,会立即挂起执行托管代码中的所有线程,正在执行非托管代码的线程不会挂起。然后,CLR检查每个线程的指令指针,判断线程指向到哪里。接着,指令指针与JIT生成的表进行比较,判断线程正在执行什么代码。
如果线程的指令指针恰好在一个表中标记好的偏移位置,就说明该线程抵达了一个安全点。线程可在安全点安全地挂起,直至垃圾回收结束。如果线程指令指针不在表中标记的偏移位置,则表明该线程不在安全点,CLR也就不会开始垃圾回收。在这种情况下,CLR就会劫持该线程。也就是说,CLR会修改该线程栈,使该线程指向一个CLR内部的一个特殊函数。然后,线程恢复执行。当前的方法执行完后,他就会执行这个特殊函数,这个特殊函数会将该线程安全地挂起。
然而,线程有时长时间执行当前所在方法。所以,当线程恢复执行后,大约有250毫秒的时间尝试劫持线程。过了这个时间,CLR会再次挂起线程,并检查该线程的指令指针。如果线程已抵达一个安全点,垃圾回收就可以开始了。但是,如果线程还没有抵达一个安全点,CLR就检查是否调用了另一个方法。如果 是,CLR再一次修改线程栈,以便从最近执行的一个方法返回之后劫持线程。然后,CLR恢复线程,进行下一次劫持尝试。
所有线程都抵达安全点或被劫持之后,垃圾回收才能使用。垃圾回收完之后,所有线程都会恢复,应用程序继续运行,被劫持的线程返回最初调用它们的方法。
实际应用中,CLR大多数时候都是通过劫持线程来挂起线程,而不是根据JIT生成的表来判断线程是否到达了一个安全点。之所以如此,原因是JIT生成表需要大量内存,会增大工作集,进而严重影响性能。
任何85000字节或更大的对象都被自动视为大对象(large object)。
大对象从一个特殊的大对象堆中分配。这个堆中采取和前面小对象一样的方式终结和释放。但是,大对象永远不压缩(内存碎片整理),因为在堆中下移850000字节的内存块会浪费太多CPU时间。
大对象总是被认为是第2代的一部分,所以只能为需要长时间存活的资源创建大对象。如果分配短时间存活的大对象,将导致第2代被更频繁地回收,进而会损害性能。
标签:
原文地址:http://www.cnblogs.com/chrisghb8812/p/5572592.html