2016-12-07(c#相关)--垃圾回收机制
--------------------------------------------自动内存管理--------------------------------------------
产生的背景:比如在C++下多少次发生了内存泄漏 new空间忘记删除。当时有很多种 做法 要去避免内存泄露。但是在c#中,明显垃圾回收器会有效的回收垃圾。
但就像 第一次作业做的 stringBuilder一样 ,有的时候让垃圾回收期 一直回收 反复创建的资源明显 不好,所以有的时候需要优化.
有一些非托管类型 的资源就必须进行正确的清理操作 了。操作系统资源 比如套接字 数据库 互斥量 等等
当进程完成初始化之后,会保留一块连续的地址空间,但是开始的时候这个地址空间并不对应实际的物理地址。
包含一个指针,记录每次新分配空间的地址位置。
c#在调用new 时 产生 newobject指令 会执行如下步骤
1>计算类型所有字段的总字节数。2>还要再加上两个64位 空间 一个存方法表 一个存什么 索引。
3>然后CLR检查空间是否够用 有空间就分配物理地址,然后PNextPtr就是里面的那个指针 越过当前空间只想下一次要分配空间的地址。
c语言中分配是从链表里面 效率比栈区慢 因为他要找 合适的空间再拆开,而且 他内存都不是连续的,而这个托管堆就是顺序存,ABCD 都连续的 效率很快。
有代龄, 就是新分配的空间认为是新一代,以前都是old代。 暂时先认为是 在判断字节数超出了托管堆能分配的范围的时候执行垃圾回收。
下面的 东西 有点绕
有个root 里面存着对象的强引用,先是遍历根,根里面的对象 会串起来 然后里面的 对象如果也引用着别的对象也给他串起来。当然如果第二次要串相同对象的时候 他会停止,避免陷入循环也提高效率。
这样剩下没有被引用的 就认为 应该删除。
回收垃圾的时候 会去看回收的空间有没有连续大的空间,小的空间会被忽略,如果有大的空间就把后面的空间往前提,这样可以 压缩托管堆,此时指向这块空间的指针也要改变了,垃圾管理器也要管别的引用这块空间的指针,把他们也改成当前位置。
然后pNextPtr也要往前提。
C++为什么不用,C++可以讲一个类型转化成另一个类型,CLR却一直知道要回收的是什么类型,不会回收错。
任何需要 释放非托管类型(内核之类的)都啊哟重写 一下Finalize方法 这样垃圾回收的时候会调用一下 Finalize然后这个函数里面一般都是cloiseHandle这样的函数。实现了对内核对象的释放。
调用Finally方法之后要 在finally里面 调用父类的 finalize方法
---------------------------------------------------------------------------------------------
Finally块 ,在try块后面执行,try捕获了异常,下面的代码可能执行不到,把下面的代码放在finally块 中,便可以确保他一定会执行到finnaly里面的代码。
尽量避免使用finalize方法。 还有为了减轻负担有 clsoeHandle(handle)这种简单的写法,也有ToHandle这种函数。
带这个函数的对象 呗垃圾回收的时候会耗费性能,放在一个终止化对象链表中。尽量不用。
Dispose() //显式的 关闭资源。 实现了Dispose的类如 Filestream f; ((IDispose)f).Dispose();
调用DIspose里面会 调用 suppressFInalize() 不在调用 Finalize 方法 所以 垃圾 清理的时候 就能正常进行了
f.close() 实现相同的功能而不需要转型 更方便 大多数 继承自IDispose的类并 不要这样的转型 来调用 Dispose()
如果自己要实现 Dispose 那么在其他函数里面 有 需要 在句柄有效存在时候才能调用执行操作的 地方要抛出异常 System.ObjectDisposeException
要谨慎的 使用 using 只有确定 资源确定要销毁了才用 using 或者 dispose 要不其他地方就要可能抛出异常了
弱引用,就是垃圾回收可以回收,他也可以被 访问 但是要看哪个先执行。
例子是 装在磁盘目录的树 声明成弱引用之后 在其他占用资源不是很紧张的 情况下 没执行垃圾回收 下一次再 需要文件路径就可以直接从 这个树的缓存里面拿。如果其他地方资源紧张,那就把这个树回收了。
在生命一个变量的弱引用时候一般都把他的强引用变成nil。
然后 通过 弱引用WeakRefrence 的Target来重新获取对象的强引用。
---------------------------------------------对象复苏-------------------------------------------
是这样。在对象显示声明了 Finalize 函数之后(就是析构的写法) 当这个对象'死亡',要被垃圾回收,当前对象会被加入到终止化可达链表中,然后调用Finalize 函数 在这个函数里面 用一个根重新获得当前对象的强引用 object=this;
这样他就会复苏。但是因为这个Finalize 已经被调用过了 所以其他地方再用可能 就会 触发 异常。此时 要加上GC.ReRegistorForFinalize(this) ;这个函数会 把当前对象再加入到 终止化可达列表的尾部。这样 可以使对象多次调用Finalize 函数。这个对象好像 永远都不会死了。所以最好加上点东西用来限制他。
-----------------------------------------------用对象复苏设计的对象池------------------------------
ClassA
~ClassA() 里面调用 if(pool) GC.ReRegistorForFinalize(this) ; //如果对象池还存在 那就执行对象复苏。 pool.push(this); //pool 其实就是stack 对象里面装的 全是 ClassA
这个 实现起来很简单。
当外界根不持有对象的强引用时候 会将对象放入终止可达链表中然后执行Finalize 如果程序还在进行(pool!=NULL) 我们就不回收 调用函数让对象可以重新执行Finalize 然后将对象返回给对象池
当需要对象就从对象池中Get一个。很方便。(适用于创建对象的成本很高的 地方)
---------------------------------------------垃圾的代龄--------------------------------------------
这是一个 非常有意思的东西。现在有 0代 1代 2代 0代是新来的 1代是至少经过一次垃圾检测的 2代是至少经过两次垃圾检测的
基于以下挺靠谱的假设。
新来的很容易是垃圾。老的东西很可能不是垃圾更倾向于继续活着。
0代有256K 1代有2M 2代有10M
每次都先看0代,0代满了清理一次。将可达对象放到一代。然后看1代满没满 没满就不管 。满了 就清清过后剩下的东西放到2代。
这样0代 1代 都空了,可以重复执行直到2代也满了,都清理一次。然后0代 1代 空 重复执行。
每次 如果1代没满,只清理0代效率在1毫秒之内,很快。
然后垃圾回收器 还是一个自调节的 他会学习?如果发现0代垃圾很多他就会把阈值缩小到124K这样执行垃圾清理的频次就会很高,应用程序的工作集不会增长的过大。
阈值会提高也会减少,1代2代也会阈值变化。
----------------------------编程控制垃圾管理器------------------------------------------
System.GC 类型可以控制垃圾管理器。
例如最大代龄。强制collection 回收某一代龄的垃圾
这个比如 有一些比较费时间的地方可以调用一下这个函数 能掩盖一下,让用户感觉不到垃圾回收的存在。

浙公网安备 33010602011771号