能简单聊一下垃圾收集的工作方式吗?
运行.NET应用程序时,程序创建出来的对象都会被CLR跟踪,
哪些对象还会被用到(存在引用关系);哪些对象不会再被用到(不存在引用关系),CLR都是有记录的。
CLR会整理不会再被用到的对象,在恰当的时机,按一定的规则销毁一部分对象,释放出这些对象所占用的内存。
-----------------
上面这段话,牵涉到了很多技术点:(以下这些技术点,简单说一下,不深入讨论)
CLR是怎么记录对象引用关系的?
CLR会把对象关系做成一个“树图”,这样标记他们的引用关系
CLR是怎么释放对象的内存的?
.NET的垃圾回收器采用 mark-and-compact 算法。在一次垃圾回收周期开始时,它识别对象的所有跟引用。并基于这个列表遍历每个跟所标识的树形结构,地递归确定所有根引用指向的对象。这样创建出一张所有可达对象图。
将所有可达对象在内存中放在一起,从而覆盖不可访问对象。
关键的技术是:CLR把没用的对象转移到一起去,使内存连续,新分配的对象就在这块连续的内存上创建,这样做是为了减少内存碎片(CLR不会移动大对象)
垃圾收集器按什么规则收集垃圾对象?
CLR按对象在内存中的存活的时间长短,来收集对象。
时间最短的被分配到第0代,最长的被分配到第2代,一共就3代。
一般第0贷的对象都是较小的对象,第2代的对象都是较大的对象
第0代对象GC收集时间最短(毫秒级别),第2代的对象GC收集时间最长。
当程序需要内存时(或者程序空闲的时),GC会先收集第0代的对象,
收集完之后发现释放的内存仍然不够用,GC就会去收集第1代,第2代对象。(一般情况是按这个顺序收集的)
如果GC跑过了,内存空间依然不够用,那么就抛出了OutOfMemoryException异常。
一个对象每次在一个垃圾回收周期中存活下来,它都会移动到下一代
既然有了垃圾收集器,为什么还要Dispose方法和析构函数?
因为CLR的缘故,GC只能释放托管资源,不能释放非托管资源(数据库链接、文件流等)
那么该如何释放非托管资源呢?
一般我们会选择为类实现IDispose接口,写一个Dispose方法。
让调用者手动调用这个类的Dispose方法(或者用using语句块来调用Dispose方法)
这是不错的选择,因为调用者最清楚该什么时候来释放这些资源。
这个方法执行时,析构函数和垃圾收集器都还没有开始处理这个对象的释放工作
-------------------------
有时候,我们不想为一个类型实现Dispose方法,
我们想让他自动的释放非托管资源。那么就要用到析构函数了。
析构函数是个很奇怪的函数,调用者无法调用对象的析构函数,析构函数是由GC调用的。
你无法预测析构函数何时会被调用,所以尽量不要在这里操作可能被回收的托管资源,析构函数只用来释放非托管资源
GC释放包含析构函数的对象,比较麻烦(需要干两次才能干掉她),
CLR会先让析构函数执行,再收集它占用的内存。
我们需要手动执行垃圾收集吗?什么场景下这么做?
GC何时执行垃圾收集是一个非常复杂的算法(策略)
大概可以描述成这样:
如果GC发现上一次收集了很多对象,释放了很大的内存,
那么它就会尽快执行第二次回收,
如果它频繁的回收,但释放的内存不多,
那么它就会减慢回收的频率。
所以,尽量不要调用GC.Collect(),这样会破坏GC现有的执行策略。
除非你对你的应用程序内存使用情况非常了解,你知道何时会产生大量的垃圾,那么你可以手动干预垃圾收集器的工作
我有一个大对象,我担心GC要过很久才会收集他,
简单聊一下弱引用和垃圾收集之间的关系?
假设有一个大对象,用完之后引用关系就没有了,这个时候GC随时都有可能收集它,并释放他占用的内存
但因为是一个较大的对象,很有可能在第2代,估计GC一时半会还不会去收集它。
这个对象已经在垃圾堆里了,但是我还想用它,怎么办?怎么从垃圾堆里把它捞回来呢?
这个时候就用到了弱引用,来看看下面这段代码:
varbss = newBsCtl(BrowserContainer);
varvbss = newWeakReference(bss);
bss = null;
BsCtl ok;
vbss.TryGetTarget(outok);
//如果没有进行垃圾收集OK不会为NULL
if(ok == null)
{
//如果已经进行了垃圾收集,就会执行这段代码
ok = newBsCtl(BrowserContainer);
}
垃圾收集随时可以收集bss对象,
如果收集了,就会进入if语句块,如果没有收集,就不会进入if语句块,TryGetTarget(out ok)就成功把bss从垃圾堆里捞回来了
垃圾收集器的好处
减少内存使用不当的BUG,提升编程效率之类的问题