《EffectiveC#》这本书讲了一些关于C#语言的使用技巧和经验. 该系列文章是备忘录和自己的一些见解.程序员们最喜欢这类问题了,欢迎讨论~
第二章主要讲资源管理相关,而GC则是这章的绝对主角。
关于GC,有太多的文章讨论这个机制,如果下列概念不太清晰的话,就意味着需要再去看看MSDN的文档了:
托管堆、分代收集算法、Root对象,引用关系图、Finalizer的调用时机,WeakReferance。
大对象和小对象,对象固定 http://www.cnblogs.com/futao/archive/2009/12/23/1630461.html
这里有一篇关于GC的算法发展的文章 http://blog.csdn.net/KAI3000/article/details/314628
个人对GC的总结:
C#使用GC作为内存管理的机制。
现阶段C#使用的是分代收集算法。
对象一共有3代:第0代,第1代,第2代
程序对象分配是在第0代和LOH(大对象堆)上进行的。只有GC程序可以在第一代进行垃圾回收。
LOH一定是第2代对象。
当程序要分配内存了,而当前的第0代或LOH空间不足,就会触发垃圾回收动作。
好的程序结构应该是在对第0代进行垃圾收集时,就可以将程序内大部分不使用的对象进行回收。
如果经过了垃圾回收还不能满足分配需求,就会进行堆扩展。
垃圾对象的判定标准是:根据”对所有Root对象进行依赖分析“,建立若干依赖关系图,所有不在这些图中的对象就是垃圾对象。
Root对象是具有强引用的对象,但反过来不成立,具有强引用的对象不一定是Root对象,也可能只是依赖关系中的某一环对象。
对所有代的垃圾回收都是阻塞主线程的,GC操作的耗时从几十毫秒~几百毫秒,甚至几秒都是有可能的。
即使对象已经被标记为垃圾对象,对它的回收也可能有两个步骤:直接回收堆内存,或者进入结束队列,等待调用Finalizer。
Finalizer队列和GC不在同一个线程。
对象的Finalizer方法和GC毛关系都没有,是处理非托管对象的释放用的。理论上来讲,可以不依赖Finalizer,而是靠良好的编程规范管理好非托管资源。
第0代和第1代的对象会被GC机制在托管堆上”移动“,为的是腾出更多的连续内存空间。这个压缩操作是在Finalizer队列执行完成之后。(话说谁在意这个时机)
第2代对象即使被回收了,但是LOH上剩余的对象也不会移动(将来可能会)。
在第2代的内存空间里,可以认为和C++等价,是会在虚拟内存中产生碎片的。
如果真关心对象的内存地址,应该将对象固定。
如果非要手工的调用GC,最好只做第0代的清理操作,频繁的调用对所有代的回收,这会导致大量的小对象进入第2代,一旦进入第2代,内存碎片的问题就越发明显,这会导致堆的尺寸变得巨大。而且手工调用GC也会干扰GC使用的策略引擎工作效率下降--该引擎基于GC的结果动态调整GC调用的阀值和频率。
class A { public A(){a = 3;} // bad public int a = 3; // good }
class A { public A() { try { _file = File.Open("C:/sdja.txt"); } catch { // do something to fix it; } } private File _file; }
using(x) using(y) { //do something }但是这种写法有潜在bug:如果构造y的时候发生了异常, x 的Dispose方法永远不会被调用。
protected bool _disposed = false; protected virutal _Dispose(bool disposing) { if(_disposed) return; if( disposing) { // free managed resource or objects, like set value 0; } // free unmanaged resource. base._Dispose(disposing); _disposed = true; } ~T() { _Dispose(false); } void Dispose() { _Dispose(true); GC.SuppressFinalize(true); }