五、GC中的代
代(Generation)引入的原因主要是为了提高性能(Performance),以避免收集整个堆(Heap)。一个基于代的垃圾回收器做出了如下几点假设:
1、对象越新,生存期越短
2、对象越老,生存期越长
3、回收堆的一部分,速度快于回收整个堆
.NET的垃圾收集器将对象分为三代(Generation0,Generation1,Generation2)。不同的代里面的内容如下:
1、G0 小对象(Size<85000Byte)
2、G1:在GC中幸存下来的G0对象
3、G2:大对象(Size>=85000Byte);在GC中幸存下来的G1对象
object o = new Byte[85000]; //large object Console.WriteLine(GC.GetGeneration(o)); //output is 2,not 0
ps,这里必须知道,CLR要求所有的资源都从托管堆(managed heap)分配,CLR会管理两种类型的堆,小对象堆(small object heap,SOH)和大对象堆(large object heap,LOH),其中所有大于85000byte的内存分配都会在LOH上进行。一个有趣的问题是为什么是85000字节?
代收集规则:当一个代N被收集以后,在这个代里的幸存下来的对象会被标记为N+1代的对象。GC对不同代的对象执行不同的检查策略以优化性能。每个GC周期都会检查第0代对象。大约1/10的GC周期检查第0代和第1代对象。大约1/100的GC周期检查所有的对象。
在初学阶段用.Net编写程序时,一直都未曾考虑过程序垃圾资源回收率的问题,那是因为老师老在课堂讲什么不用管,不用理会,一听到不用理会,好吧,从此写程序就肆无忌惮的了!程序卡死、内存暴涨、顺便偶尔来几个内存错误,一看到这个就头大了。现在想想,课堂老师讲的那句话,却只听进了前半句。。。
闲聊无事,也不用再怕什么在职防止泄露啥啥机密、啥啥技术的、、、嘎嘎、、、、(下面的纯属个人观点,如有雷同、敬请绕道、、、)
在.Net里面垃圾收集的工作方式:
运行.NET应用程序时,程序创建出来的对象实例都会被CLR跟踪,CLR都是有记录哪些对象还会被用到(存在引用关系);哪些对象不会再被用到(不存在引用关系)。CLR会整理不会再被用到的对象,在恰当的时机,按一定的规则销毁部分对象,释放出这些对象所占用的内存。
说到这里,那就引出了新的技术点:
CLR是怎么记录对象引用关系的?
CLR会把对象关系做成一个“树图”,这样标记他们的引用关系
CLR是怎么释放对象的内存的?
关键的技术是:CLR把没用的对象转移到一起去,使内存连续,新分配的对象就在这块连续的内存上创建,这样做是为了减少内存碎片。注意!CLR不会移动大对象
垃圾收集器按什么规则收集垃圾对象?
CLR按对象在内存中的存活的时间长短,来收集对象。时间最短的被分配到第0代,最长的被分配到第2代,一共就3代。
一般第0贷的对象都是较小的对象,第2代的对象都是较大的对象,第0代对象GC收集时间最短(毫秒级别),第2代的对象GC收集时间最长。当程序需要内存时(或者程序空闲的时),GC会先收集第0代的对象,
收集完之后发现释放的内存仍然不够用,GC就会去收集第1代,第2代对象。(一般情况是按这个顺序收集的)
如果GC跑过了,内存空间依然不够用,那么就抛出了OutOfMemoryException异常。
GC跑过几次之后,第0代的对象仍然存在,那么CLR会把这些对象移动到第1代,第1代的对象也是这样。
既然有了垃圾收集器,为什么还要Dispose方法和析构函数?
因为CLR的缘故,GC只能释放托管资源,不能释放非托管资源(数据库链接、文件流等)。
那么该如何释放非托管资源呢?
一般我们会选择为类实现IDispose接口,写一个Dispose方法。
让调用者手动调用这个类的Dispose方法(或者用using语句块来自动调用Dispose方法)
Dispose执行时,析构函数和垃圾收集器都还没有开始处理这个对象的释放工作
有时候,我们不想为一个类型实现Dispose方法,我们想让他自动的释放非托管资源。那么就要用到析构函数了。
析构函数是个很奇怪的函数,调用者无法调用对象的析构函数,析构函数是由GC调用的。
你无法预测析构函数何时会被调用,所以尽量不要在这里操作可能被回收的托管资源,析构函数只用来释放非托管资源
GC释放包含析构函数的对象,比较麻烦(需要干两次才能干掉她),
CLR会先让析构函数执行,再收集它占用的内存。
我们需要手动执行垃圾收集吗?什么场景下这么做?
GC什么时候执行垃圾收集是一个非常复杂的算法(策略)
大概可以描述成这样:
如果GC发现上一次收集了很多对象,释放了很大的内存,
那么它就会尽快执行第二次回收,
如果它频繁的回收,但释放的内存不多,
那么它就会减慢回收的频率。
所以,尽量不要调用GC.Collect()这样会破坏GC现有的执行策略。
除非你对你的应用程序内存使用情况非常了解,你知道何时会产生大量的垃圾,那么你可以手动干预垃圾收集器的工作
我有一个大对象,我担心GC要过很久才会收集他,
关于弱引用和垃圾收集之间的关系?
当一个大对象被使用后不存在引用关系时,GC就会自动回收它占用的内存。
当这个对象足够大的情况下,GC在回收它时,可能时间稍微会长点,当用户需要再次使用该对象时,我们可以从回收池中再次提取该对象,这里就涉及到弱引用,代码如下:
垃圾收集随时可以收集bss对象,
如果收集了,就会进入if语句块,如果没有收集,就不会进入if语句块,TryGetTarget(out ok)就成功把bss从垃圾堆里捞回来了。
注意:这里只说了短弱引用,没有提及长弱引用,我觉得长弱引用使用的场景较少。
垃圾收集器优点:
因为我没有很丰富的C/C++编程经验,如果想谈垃圾收集器的好处,那么势必要和C/C++这样的较低级的语言对比。所以一般性的回答都是减少内存使用不当的BUG,提升编程效率之类的问题。