今天终于搞明白了一些,先记下,免得忘了。
需要了解的概念(转自http://blog.sina.com.cn/s/blog_5aeeb8200100blua.html):
a.其中托管资源一般是指被CLR控制的内存资源,这些资源的管理可以由CLR来控制,例如程序中分配的对象,作用域内的变量等。
b.而非托管资源是CLR不能控制或者管理的部分,这些资源有很多,比如文件流,数据库的连接,系统的窗口句柄,打印机资源等等……这些资源一般情况下不存在于Heap(内存中用于存储对象实例的地方)中。
以下是我自己的总结:
1.Finalize方法是用来隐式的释放非托管资源,只有类型中包含非托管资源时才需要实现。Finalize方法的调用很耗时,因为当CLR发现一个对象不可达并想要对他进行垃圾回收时,如果该对象实现了Finalize方法则暂时不会对他进行回收,而是将它置于“带析构表”中,之后由一个专门的线程来调用该对象的Finalize方法,当下次进行垃圾回收时再将其回收
来自MSDN:
下面的规则概括了 Finalize 方法的使用准则:
-
仅在要求终结的对象上实现 Finalize。存在与 Finalize 方法相关的性能开销。
-
如果需要 Finalize 方法,应考虑实现 IDisposable,以使类的用户可以避免因调用 Finalize 方法而带来的开销。
-
不要提高 Finalize 方法的可见性。该方法的可见性应该是 protected,而不是 public。
-
对象的 Finalize 方法应该释放该对象拥有的所有外部资源。此外,Finalize 方法应该仅释放由该对象控制的资源。Finalize 方法不应该引用任何其他对象。
-
不要对不是对象的基类的对象直接调用 Finalize 方法。在 C# 编程语言中,这不是有效的操作。
-
应在对象的 Finalize 方法中调用基类的 Finalize 方法。
注意 基类的 Finalize 方法通过 C# 和 C++ 析构函数语法自动进行调用。
2.Dispose方法用来提供给程序员手动的显示的释放资源,既然是显式,就可以在此释放托管资源和非托管资源。但是如果类型不持有非托管资源,也是没有必要实现该接口的。(还需要验证)
来自MSDN:
下面的规则概括了 Dispose 方法的使用准则:
-
在封装明确需要释放的资源的类型上实现释放设计方案。用户可以通过调用公共 Dispose 方法释放外部资源。
-
在通常包含控制资源的派生类型的基类型上实现释放设计方案,即使基类型并不需要也如此。如果基类型有 Close 方法,这通常指示需要实现 Dispose。在这类情况下,不要在基类型上实现 Finalize 方法。应该在任何引入需要清理的资源的派生类型中实现 Finalize。
-
使用类型的 Dispose 方法释放该类型所拥有的所有可释放资源。
-
对实例调用了 Dispose 后,应通过调用 GC.SuppressFinalize 方法禁止 Finalize 方法运行。此规则的一个例外是当必须用 Finalize 完成 Dispose 没有完成的工作的情况,但这种情况很少见。
-
如果基类实现了 IDisposable,则应调用基类的 Dispose 方法。
-
不要假定 Dispose 将被调用。如果 Dispose 未被调用,也应该使用 Finalize 方法释放类型所拥有的非托管资源。
-
当资源已经释放时,在该类型上从实例方法(非 Dispose)引发一个 ObjectDisposedException。该规则不适用于 Dispose 方法,该方法应该可以在不引发异常的情况下被多次调用。
-
通过基类型的层次结构传播对 Dispose 的调用。Dispose 方法应释放由此对象以及此对象所拥有的任何对象所控制的所有资源。例如,可以创建一个类似 TextReader 的对象来控制 Stream 和 Encoding,两者均在用户不知道的情况下由 TextReader 创建。另外,Stream 和 Encoding 都可以获取外部资源。当对 TextReader 调用 Dispose 方法时,TextReader 应继而对 Stream 和 Encoding 调用 Dispose,使它们释放其外部资源。
-
考虑在调用了某对象的 Dispose 方法后禁止对该对象的使用。重新创建已释放的对象是难以实现的方案。
-
允许 Dispose 方法被调用多次而不引发异常。此方法在首次调用后应该什么也不做。
以下是《框架设计 CLR Via c#》一书中作者的建议:
一般情况下,我个人建议大家不要使用Dispose方法或者Close方法,理由是CLR的垃圾收集器已经得到了很好的实现。我们完全可以将工作交给他来做,垃圾收集器知道一个对象何时不再被应用程序代码访问,直到做出这样的判定后他才会考虑收集对象,而当应用程序代码调用Dispose方法或Close方法时,它实际上是在表明自己知道应用程序已经不再需要使用该对象了。对于许多应用程序来说,者往往是不可能的。
例如,我们的代码构造了一个新的对象,然后又将该对象的引用传递给了另外一个方法,而该方法可能会将该对象的引用保存在自己的某个内部字段变量中(成为一个根)。调用方法可能并不知道这些情况。当然,调用方法可以调用该对象的Dispose方法或者Close方法,但是,稍后到那个其他的代码再试图访问该对象时,系统很可能抛出ObjectDisposeException异常。但是,在需要显示的清理资源(例如试图删除一个打开的文件)或者确信这两个方法的操作时安全的情况下,如果我们希望通过将对象从终结链表中移除以提高性能从而阻止对象的代提升,建议调用Dispose方法或者Close方法。
我理解Jeffrey Richter的意思就是当对象包含非托管资源时应该使用Dispose方法或Close方法来显式的清除资源代替使用Finalize方法,但是当对象不包含托管资源时,没有必要实现Dispose方法或Close方法,因为程序员不一定能够精确地掌握对象的生存期而ClR可以。
3.由于Dispose方法和Finalize方法的意义相同,只是一个显示调用,一个隐式调用。所以这两个只需要调用一次即可,可以使用下面的模式实现。
class Base:IDisposable
{
private bool _isDisposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(true);//Tell CLR Do Not Call Finalize()
}
~Base()
{
Dispose(false);
}
virtual public void Dispose(bool isDispose)
{
if (_isDisposed)
{
return;
}
if (isDispose)
{
//release trusteeship resource
}
//release untrusteeship resource
_isDisposed = true;
}
}
/// <summary>
/// Derive don't have Dispose and Finalize function because of derive from base
/// </summary>
class Derive : Base
{
private bool _myDispose = false;
public override void Dispose(bool isDispose)
{
if (_myDispose)
{
return;
}
if (isDispose)
{
//release trusteeship resource
}
base.Dispose(isDispose);
//release untrusteeship resource
_myDispose = true;
}
}
4.对象分为3各代,0代,1代,2代,对象越新,生存期越短,对象越老,生存期越长(该前提经过大量研究)刚分配的对象都是0代。CLR初始化时,会为每个代选择一个预算容量,0代大约为256KB,1代大约为2M,2代大约为10M,不过这仅仅是个预算而已,.Net的垃圾回收器会基于应用程序所加载的内存自动调整预算容量,从而可以提高性能。
当需要分配新的对象而0代的内存满了的时候,垃圾回收器开始对0代进行回收,并将未被回收的对象标识为1代(对象的代龄可以通过GC的int GetGeneration(object obj)函数获得),若1代内存满了则进行1代的回收,并将对象的带领增加。由此可以看到,垃圾回收器回收不同代的次数是成倍数关系的,执行N此M代的回收才会执行一次M+1代的回收,所以第一节提到的第一次准备回收Finalize对象时并没有回收,需要将其的代龄+1,这样一来,下次回收该对象就是N次以后的事了。
垃圾回收机制过程:
1.垃圾回收时机:托管堆满了,内存分配即将不足时,0代内存分配满了,或其他情况,微软没有公开该部分算法。程序员可以手动调用GC.Collect(),但是会有警告,微软并不建议这么做。
2.垃圾确认:通过根来寻找可达的对象(以后添加),并做标记,然后回收没有标记的对象。
3.垃圾回收:内存回收,对于实现了Finalize方法的对象请参考最上面1的介绍。
4.内存转移,合并。垃圾回收后使得内存不连续,零碎,.Net会将利用的内存合并为连续的块,然后更新对象的指针。
由此可见,垃圾回收是相当的耗时。写的面有点广,不系统,很乱,还需要整理。