c#垃圾回收机制

自动内存管理

产生的背景:比如在C++下多少次发生了内存泄漏 new空间忘记删除。当时有很多种做法 要去避免内存泄露。但是在c#中,明显垃圾回收器会有效的回收垃圾(无引用了就触发GC回收垃圾)。
但就像第一次作业做的 stringBuilder一样 ,有的时候让垃圾回收 一直反复创建的资源明显不好,所以有的时候需要优化.

有一些非托管类型 的资源就必须进行正确的清理操作 了。操作系统资源 比如套接字 数据库 互斥量 等等
当进程完成初始化之后,会保留一块连续的地址空间,但是开始的时候这个地址空间并不对应实际的物理地址。
包含一个指针,记录每次新分配空间的地址位置。

c#在调用new 时 产生 newobject指令 会执行如下步骤
1>计算类型所有字段的总字节数。
2>还要再加上两个64位 空间 一个存方法表 一个存什么 索引。
3>然后CLR检查空间是否够用 有空间就分配物理地址,然后PNextPtr就是里面的那个指针 越过当前空间记下下一次要分配空间的地址。

c语言中分配是从链表里面 效率比栈区慢 因为他要找 合适的空间再拆开,而且他内存都不是连续的(还有内部碎片 外部碎片 很操蛋),而这个托管堆就是顺序存,ABCD 都连续的 效率很快。

垃圾回收

有代龄, 就是新分配的空间认为是新一代,以前都是old代。 暂时先认为是 在判断字节数超出了托管堆能分配的范围的时候执行垃圾回收。
下面的东西有点绕 但挺有意思,代龄的设定很关键
有个root 里面存着对象的强引用,先是遍历根,根里面的对象会串起来 然后里面的 对象如果也引用着别的对象也给他串起来。当然如果第二次要串相同对象的时候 他会停止,避免陷入循环也提高效率。
这样剩下没有被引用的 就认为 应该删除。

回收垃圾的时候 会去看回收的空间有没有连续大的空间,小的空间会被忽略,如果有大的空间就把后面的空间往前提,这样可以 压缩托管堆,此时指向这块空间的指针也要改变了,垃圾管理器也要管别的引用这块空间的指针,把他们也改成当前位置(貌似这样会改很多)。
然后pNextPtr也要往前提。

??C++为什么不用,C++可以讲一个类型转化成另一个类型(还记得c++删除的时候按类型删多么恶心人么。。),CLR却一直知道要回收的是什么类型,不会回收错。

任何需要 释放非托管类型(内核之类的)都要重写 一下Finalize方法 这样垃圾回收的时候会调用一下 Finalize然后这个函数里面一般都是closeHandle这样的函数。实现了对内核对象的释放。
调用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 回收某一代龄的垃圾
这个比如 有一些比较费时间的地方可以调用一下这个函数 能掩盖一下,让用户感觉不到垃圾回收的存在。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值