托管堆和垃圾回收(GC) (2)

如果GC回收第0代时发现回收了大量内存,则会缩减第0代的预算,这意味着GC更频繁,但做的事情也减少了;反之,如果发现没有多少内存被回收,就会增大第0代的预算,这意味着GC次数更少,但每次回收的内存相对要多。对于第1代和第2代对象来说,也是如此。

如果回收后发现仍然没有得到足够的内存且无法增大预算,GC就会执行一次完全垃圾回收,如果还不够,就会抛出OutOfMemoryException异常。

四、何时进行垃圾回收

应用程序new一个对象时,CLR发现没有足够的第0代对象预算来分配该对象时

代码显式调用System.GC.Collect()方法时。注意不要滥用该方法

Windows报告低内存情况时

CLR正在卸载AppDomain时。会回收该AppDomain的所有代对象

CLR正在关闭时。CLR在进程正常终止(而不是通过任务管理器等外部终止)时关闭,会回收进程中的所有对象。

五、垃圾回收模式

CLR启动时,会选择一个GC主模式,该模式不会更改,直到进程终止。

工作站:默认的,针对客户端应用程序进行优化。GC造成的时延很低,不会导致UI线程出现明显的假死状态

服务器:针对服务器端应用程序进行优化,主要是优化吞吐量和资源利用。

可以在配置文件中告诉CLR使用服务器回收模式:

<configuration> <runtime> <gcServer enabled="true"/> </runtime> </configuration>

另外,GC还支持两种子模式:并发(默认)和非并发。主要区别在于并发模式中GC有一个额外的后台线程,它能在应用程序运行时并发标记对象。可以在配置文件中告诉CLR不要使用并发回收模式:

<configuration> <runtime> <gcConcurrent enabled="false"/> </runtime> </configuration>

当然,你也可以通过GCSetting类的GCLatencyMode属性对垃圾回收进行某些控制(在你没有完全了解影响的情况下,强烈建议不要更改):

模式 说明
Batch   关闭并发GC,.net framework 版本服务器模式默认值  
Interactive   打开并发GC,工作站模式与 .net core 版本服务器模式的默认值  
LowLatency   在短期的、时间敏感的操作中(如动画绘制)使用这个低延迟模式,该模式会尽力阻止第2代垃圾回收,因为花费时间较多,只有当内存过低时才会回收第2代。  
SustainedLowLatency   这个低延迟模式不会导致长时间的GC暂停,该模式会尽力阻止非并发GC线程对第2代垃圾回收(但是允许后台GC线程对其的回收),只有当内存过低时才会阻塞回收第2代,适用于需要迅速响应的应用程序(如股票等)。  

另外,还有一个模式叫做NoGCRegion,用于在程序执行关键路径时将GC线程挂起。但是你不能将该值直接赋值给GCLatencyMode属性,要通过调用System.GC.TryStartGCRegion方法才可以,并调用System.GC.EndGCRegion方法结束。

六、注意事项

静态字段引用的对象会一直存在,直到用于加载类型的AppDomain卸载为止

由于碎片整理的开销相对较大,因此GC在划算时才会进行碎片整理,并非每次都会执行。

大对象始终为第2代,而且目前版本GC不会压缩大对象,因为移动代价过高。

第0代和第1代总是位于同一个内存段,而第2代可能跨越多个内存段。

七、特殊的Finalize(终结器)

包含本机资源的类型被GC时,GC会回收对象在托管堆中使用的内存。但这样会造成本机资源的泄漏,为了处理这种情况,CLR提供了称为终结的机制——允许对象在判定为垃圾之后,但在对象内存被回收前执行一些代码。在C#中的表示如下:

class SomeType { // 这是一个 Finalize 方法 ~SomeType() { } }

其生成的IL代码为:

托管堆和垃圾回收(GC)

可以看到,C#编译器实际是在模块的元数据中生成了名为Finalize的protected override方法,并且方法主体的代码被放置在try块中,并在finally块中调用base.Finalize(本例调用了Object的终结器)。

那么,终结的内部是如何工作的呢?

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wssjyy.html