CLR via C#:托管堆和垃圾回收

托管堆:指的是进程初始化时,CLR开辟的一个地址空间区域。具有以下特性:
1.存储大对象的托管堆称为大对象堆;存放小对象的托管堆称为小对象堆。
2.托管堆内部维护一个NextObjPtr指针来指向下一个对象的分配位置。
3.静态字段引用的对象一直存在,直到用于加载类型的AppDomain卸载为止。

:指的是引用类型的变量。具有以下特性:
1.当C#编译器使用/debug开关时,根的生命周期延长至函数的生命周期结束。
2.当C#编译器不使用/debug开关时,根的生命周期不保证在函数的生命周期中自始至终的存活。

:指的是托管堆中的区域。具有以下特性:
1.CLR现在只支持三代,分别为第0代,第1代以及第2代。
2.CLR初始化以及垃圾回收后都会动态调整三代各自的预算。
3.代对代码做出以下假设:
1>.对象越新,生存期越短。
2>.对象越旧,生存期越长。
3>.回收托管堆的一部分,速度快于回收整个托管堆。

大对象:指的是占用内存大于或等于85000字节的对象。具有以下特性:
1.大对象存放在大对象堆的第2代中。
2.垃圾回收器不对存活的大对象进行移动,从而造成大对象堆的第2代地址空间碎片化。

访问资源:流程如下所示:
1.调用IL的newobj指令,为代表资源的类型分配内存。
2.初始化内存,设置资源初始化状态并使资源可用。
3.访问类型的成员来使用资源。
4.由垃圾回收器自动释放内存。

分配资源:CLR使用new操作符来分配资源。流程如下所示:
1.计算类型的字段(包含从基类继承的字段)所需的字节数。
2.计算对象的开销(包含类型对象指针和同步块索引)所需的字节数。
3.当对象是大对象时,执行流程如下所示:
1>.CLR检查大对象堆的第2代没有超过预算时,就会在大对象堆的NextObjPtr指针指向的地址处放入大对象,并将大对象分配的字节清零;然后调用类型的实例构造函数(为this参数传递NextObjPtr),并在new操作符返回大对象引用之前将大对象堆的NextObjPtr指针的值加上大对象占用的字节数,从而得到下一个大对象放入大对象堆的第2代时的地址。
2>.CLR检查大对象堆的第2代超过预算时,就会执行垃圾回收。如果没有回收足够内存时,就会抛出OutOfMemoryException;否则就执行步骤1>。
4.当对象是小对象时,执行流程如下所示:
1>.CLR检查小对象堆的第0代没有超过预算时,就会在小对象堆的NextObjPtr指针指向的地址处放入小对象,并将小对象分配的字节清零;然后调用类型的实例构造函数(为this参数传递NextObjPtr),并在new操作符返回小对象引用之前将小对象堆的NextObjPtr指针的值加上小对象占用的字节数,从而得到下一个小对象放入小对象堆的第0代时的地址。
2>.CLR检查小对象堆的第0代超过预算时,就会执行垃圾回收。如果没有回收足够内存时,就会抛出OutOfMemoryException;否则就执行步骤1>。

垃圾回收触发条件:如下所示:
1.开发人员在代码中主动调用GC的Collect函数时,就会执行一次垃圾回收。
2.CLR在分配资源的过程中检查代占用内存超过预算时,就会执行一次垃圾回收。
3.CLR内部使用CreateMemoryResourceNotification和QueryMemoryResourceNotification函数监视系统报告低内存时,就会执行一次垃圾回收。
4.CLR正在卸载AppDomain时,就会执行一次垃圾回收。
5.CLR正在关闭时,所有对象有机会进行资源清理,系统也将回收进程的全部内存。

垃圾回收模式:具有以下特性:
1.工作站主模式:该模式针对客户端应用程序优化垃圾回收。
2.服务器主模式:该模式针对服务端应用程序优化垃圾回收。
3.并发子模式:垃圾回收器有一个额外的后台线程,它能在应用程序运行时并发标记对象。
4.非并发子模式。
5.CLR启动时会选择一种主模式和子模式。可以在应用程序配置文件中通过gcServer元素的enabled属性来设置是否使用服务器主模式;通过gcConcurrent元素的enabled属性来设置是否使用并发子模式。
6.可以使用GCSettings类的IsServerGC属性来判定是否处于服务器主模式。
7.可以使用GCSettings类的LatencyMode属性来对垃圾回收进行某种程度的控制。
常见控制如下表所示:

符号名称说明
Batch(服务器主模式的默认值)关闭并发子模式
Interactive(工作站主模式的默认值)打开并发子模式
LowLatency在短期的,时间敏感的操作中使用这个延迟模式。这些操作不适合对第2代进行回收
SustainedLowLatency使用这个延迟模式,应用程序的大多数操作都不会发生长的垃圾回收暂停。只要有足够的内存,它将禁止所有会造成阻塞的第2代回收动作。

垃圾回收执行过程:CLR采用引用跟踪算法,该算法只关心根。执行流程如下所示:
1.CLR暂停进程中的所有线程,从而防止线程访问对象并更改其状态。
2.CLR遍历小对象堆的三代以及大对象堆的第2代,并将"代的占用内存"超过"代的预算"的代中所有对象添加到"代对象列表"中。
3.CLR进入标记阶段。该阶段执行流程如下所示:
1>.CLR将代对象列表中所有对象标记成可回收(也就是将对象的同步块索引字段中的一位设置成0)。
2>.CLR查找所有活动根,并执行以下流程:
1>>.根引用null时,CLR就会忽略这个根并继续检查下一个根。
2>>.根引用对象时,当CLR检查该对象不在代对象列表中或者该对象已经被标记成不可回收时,就会忽略这个根并继续检查下一个根;否则就会将该对象标记成不可回收(也就是将对象的同步块索引字段中的一位设置成1),然后将该对象中的所有根都执行1>>和2>>步骤。
4.CLR进入压缩阶段,并执行以下流程:
1>.将代对象列表中不可回收的小对象进行移动(也就是先将第1代小对象移动到第2代,然后将第0代的小对象移动到第1代),使它们占用连续的内存空间。
2>.CLR将根减去引用小对象在内存中偏移的字节数,从而使根还是引用之前一样的小对象。
5.CLR将小对象堆的NextObjPtr指针指向最后一个不可回收对象之后的位置,该位置就是小对象堆的第0代起始位置。
6.CLR将大对象堆的NextObjPtr指针指向某一个回收大对象的位置,该位置就是大对象堆的第2代起始位置。
7.CLR恢复进程中的所有线程,这些线程可以继续访问对象,就像没有进行垃圾回收一样。

强制垃圾回收:就是使用GC类型对应用程序进行一些控制。具有以下特性:
1.GCCollectionMode枚举定义如下表所示:

符号名称说明
Default等同于不传递任何符号名称。目前还等同于传递Forced,但CLR未来版本可能对此进行修改
Forced强制回收指定的代(以及低于它的所有代)
Optimized只有在能释放大量内存或者能减少碎片化的前提下,才执行回收。如果垃圾回收没有任何效益,当前调用就没有任何效果

2.Collect函数用来强制垃圾回收。一般情况下不建议调用该函数,而是让垃圾回收器自行斟酌执行。但是以下情形可以考虑手动调用该函数:
1>.发生了某个非重复性事件,并导致大量旧对象死亡,就可以手动调用一次Collect函数。
3.对于一次垃圾回收需要花很长时间才能完成时,可以使用RegisterForFullGCNotification,WaitForFullGCApproach,WaitForFullGCComplete以及CancelForFullGCNotification等函数来使应用程序在垃圾回收器将要执行完全回收时收到通知,从而做出相应的逻辑处理。
4.CollectionCount函数用来查看某一代发生了多少次垃圾回收。
5.GetTotalMemory函数用来查看托管堆中对象当前使用了多少内存。
6.AddMemoryPressure函数用来监视内存压力变大时,就强制执行垃圾回收。
7.RemoveMemoryPressure函数用来取消监视内存压力。
8.HandleCollector类型对象会在内部监视资源的计数,当计数超过阈值时就强制垃圾回收。

终结机制:一种用来释放本机资源而设计的机制。具有以下特性:
1.C#要求在类名前添加~符号来定义Finalize函数。
2.终结机制内部工作原理如下所示:
1>.在调用对象的构造函数前,如果该对象重写了Object的Finalize函数的话,CLR就会将指向该对象的引用存放在终结列表中。
2>.一次垃圾回收时,垃圾回收器在托管堆上的标记可回收对象时,如果该对象在在终结列表中存在引用的话,CLR就会从终结列表中移除该对象的引用;然后将该对象的引用存放在freachable队列中;最后将托管堆上该对象标记成不可回收。
3>.CLR使用特殊的终结线程来检查freachable队列是否为空。当freachable队列为空时,该线程就会休眠;否则该线程就会被唤起,然后清空freachable队列并执行每个对象的Finalize函数。
4>.下一次进行垃圾回收时,已终结的对象要被垃圾回收器真正的回收掉,必须满足以下条件:
1>>.垃圾回收器要回收的代中包含已终结的对象。
2>>.没有根关联到已终结对象。
3.Finalize函数问题较多,使用须谨慎。常见的问题如下所示:
1>.Finalize函数的执行时间是没法手动控制的。
2>.Finalize函数的执行顺序是没法手动控制的。
3>.调用了Finalize函数的对象会被提升到下一代,直到下一次进行垃圾回收时才有可能被回收掉,从而增大内存耗用。
4>.如果某一个Finalize函数发生阻塞,终结线程就调用不了其他的Finalize函数,从而造成本机资源内存泄漏。
4.强烈建议不要重写Object的Finalize函数,而是使用FCL提供的辅助类来完成终结操作。常见的辅助类如下所示:
1>.CriticalFinalizerObject抽象类具有以下特性:
1>>.首次构造CriticalFinalizerObject派生类型的对象时,CLR立即对继承层次结构中的所有
Finalize函数进行JIT编译。
2>>.CLR是在调用了非CriticalFinalizerObject派生类型的Finalize函数之后,才调用CriticalFinalizerObject派生类型的Finalize函数。
3>>.如果AppDomain被一个宿主应用程序强行中断,CLR将调用CriticalFinalizerObject派生类型的Finalize函数,从而确保本机资源得以释放。
2>.SafeHandle抽象类具有以下特性:
1>>.具有CriticalFinalizerObject抽象类的所有特性。
2>>.SafeHandle派生类型必须重写受保护的构造函数,释放资源的抽象函数ReleaseHandle以及是否句柄无效的抽象属性IsInvalid。
3>>.与本机代码互操作时,SafeHandle派生类型将获得CLR的特殊支持。如:自动在托管堆上构建SafeHandle派生类型实例。
4>>.SafeHandle派生类型内部通过引用计数来防止有人利用潜在的安全漏洞。其中DangerousAddRef函数用来增加引用计数;DangerousRelease函数用来减少引用计数;DangerousGetHandle函数用来获取原始句柄。
3>.CriticalHandle抽象类具有以下特性:
1>>.不提供引用计数功能,其他方面与SafeHandle抽象类型相同。
2>>.由于没有引用计数,所以CriticalHandle的性能要高于SafeHandle;但是CriticalHandle的安全性要低于SafeHandle。
5.dispose模式:实现了IDisposable接口,就实现了dispose模式。具有以下特性:
1>.类型中的Dispose函数,在调用了一次之后再次调用时就直接返回。
2>.类型中的非Dispose函数和属性,在调用了一次Dispose函数之后再次调用时就抛出一个ObjectDisposedException。
3>.Dispose函数不会将对象从托管堆上回收,只是标记对象被清理而已。
4>.对支持终结机制的类型而言(如FileStream),尽量不要在代码里显示调用Dispose函数,而是应该交给垃圾回收器去回收对象。
5>.对不支持终结机制的类型而言(如StreamWriter),就要在代码里显示调用Dispose函数来清理对象。

GC句柄表:允许应用程序监视或者手动控制对象的生存期。具有以下特性:
1.GCHandleType用来指定监视或者控制对象的标志。其定义如下表所示:

标志描述
Weak可监视垃圾回收器在什么时候判定对象在应用程序中不可达(也就是标记为可回收),此时对象的Finalize函数可能已经执行,对象可能还在内存中。
WeakTrackResurrection可监视垃圾回收器在什么时候判定对象在应用程序中不可达(也就是标记为可回收),此时对象的Finalize函数已经执行,对象的内存已经被回收。
Normal可控制垃圾回收器在应用程序中没有根引用对象时,该对象也必须留在内存中。当垃圾回收发生时,该对象的内存可以被压缩(移动)。
Pinned可控制垃圾回收器在应用程序中没有根引用对象时,该对象也必须留在内存中。当垃圾回收发生时,该对象的内存不能被压缩(移动)。

2.垃圾回收器和GC句柄表的交互行为如下所示:
1>.垃圾回收器扫描GC句柄表;然后将所有Normal或Pinned对象都标记成不可回收。
2>.垃圾回收器扫描GC句柄表;如果一个Weak记录项引用了可回收对象,那么该记录项引用值更改为null。
3>.垃圾回收器扫描GC句柄表;如果一个WeakTrackResurrection记录项引用了可回收对象,那么该记录项引用值更改为null。
4>.垃圾回收器进行内存压缩(移动)时,Pinned对象不会被移动。
3.C#的fixed语句可以在一个代码块中固定对象。
4.WeakReference类型用来保存指定对象的弱引用,并且控制标志为Weak。
5.ConditionalWeakTable类用来将对象和数据进行关联,其中对象保持弱引用,并且控制标志为Weak。当对象不被回收时,数据就一定存在。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值