.Net 自动内存管理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了.Net 自动内存管理相关的知识,希望对你有一定的参考价值。

参考技术A .Net依赖CLR(公共语言运行时)实现自动内存管理,标准CLR使用分代式标记-压缩GC对托管堆上对象进行自动内存管理。

1、GC过程

(1)标记阶段:从应用程序的根找到所有可达对象进行标记并创建一个对象引用图;

每个应用程序都有一组根,应用程序的根包含线程堆栈上的静态字段、局部变量和参数以及CPU寄存器。垃圾回收器可以访问由实时编译器(JIT)和运行时维护的活动根的列表。

(2)清除阶段:

a、未标记对象如果没有终结器(析构函数)则立即回收;

b、有终结器对象在创建时会将对象引用放到终结队列,当此对象变为垃圾时,垃圾收集器会将其引用从终结队列移到f-reachable队列。GC完成后一个终结器线程会遍历f-reachable队列,获取每个对象执行其Finalize方法,待该对象的Finalize方法执行完后,将该对象引用从f-reachable队列移除。这些对象会在下一次GC中回收(除非该对象复活);

如果某个终结器对象在终结器过程中复活(Resurrection),但是复活时不会将对象的引用重新放到终结队列,如果想让复活对象下次回收时执行Finalize方法,可以在对象复活时手动调用GC.ReRegisterForFinalize(obj)将该对象的引用添加到终结队列。

另外GC.SuppressFinalize(Object obj)可以将对象的引用从终结队列移除,垃圾回收时不再执行Finalize方法。一般SuppressFinalize用于同时实现了Finalize和Dispose方法来释放资源的情况下,在Dispose方法中调用GC.SuppressFinalize(this)。Finalize方法作为忘记调用Dispose释放资源的一个保障。

(3)压缩阶段:清除完后将所有存活对象移到堆的起始位置。压缩可以防止碎片化,同时避免耗时的空内存片段列表维护,直接用简单的策略将堆的尾部内存分配给新对象。

2、GC触发时机:

(1)由托管堆上已分配对象的使用内存超过特定阈值(该阈值可能随着进程的运行不断调整)时;

(2)调用System.GC.Collect手动触发;

(3)系统具有低的物理内存。通过OS的内存不足通知或主机指示的内存不足检测出来。

3、CLR中GC优化

3.1、分代回收

垃圾回收器将堆上内存对象分为3代:

第0代:新分配对象及从未经过垃圾回收的对象集合。通常仅有几百KB到几MB;

第1代:第0代回收中存活的对象集合;

第2代:第1代和第2代中未回收的对象集合。

因此可以单独处理长生存期和短生存期对象。每一代都维护一个阀值,当第n代内存达到该阀值时会触发对第n代的收集,当垃圾回收器检测到某个代中的幸存率很高时,会增加该代的分配阈值。回收一代时同时回收它前面的所有代。

3.2 、大对象堆(Large Object Heap,LOH)

加载CLR时,GC分配两个初始堆段:一个用于小型对象(小对象堆SOH),一个用于大型对象(大对象堆 LOH)。垃圾回收器将大于等于一个阈值(目前是85000字节)的对象分配到大对象堆。大对象堆上对象都按第2代处理,可以避免过量的第0代回收。由于复制大型对象代价太大,默认不压缩大对象堆,所以需要维护空闲内存块链表,并会产生碎片化问题。在.Net Core和.Net Framework 4.5.1以上版本中,可以使用 GCSettings.LargeObjectHeapCompactionMode  属性按需压缩大对象堆。

3.3、并发和后台回收

(1)并发垃圾回收

仅适用于工作站垃圾回收的.Net Framework 3.5及更早版本;服务器垃圾回收.Net Framework 4及更早版本。更高版本中,后台垃圾回收取代了并发垃圾回收。

并发垃圾回收只影响第2代垃圾回收,第0代和第1代的垃圾回收始终是非并发的。并发垃圾会输在一个专用线程上执行,运行并发垃圾回收线程的大多数时间,托管线程可以继续运行,最大程度减少因回收引起的暂停。

(2)后台垃圾回收

在.Net Framework 4 及更高版本中,后台垃圾回收替换并发垃圾回收。但在.Net Framework 4 中仅支持工作站垃圾回收,.Net Framework 4.5开始,后台垃圾回收可用于工作站和服务器垃圾回收。

后台垃圾回收只适用于第2代回收,后台垃圾回收在一个或多个专用线程上执行行,后台垃圾回收进行中,后台垃圾回收线程将在常见的安全点上检查,如果发现第0代或第1代空间不足需要前台垃圾回收时,后台垃圾回收会暂停自己并让前台垃圾回收(对暂时代(第0代和第1代)的回收)执行。前台垃圾回收完成之后,后台回收线程和用户线程将继续。

3.4、工作站和服务器垃圾回收

CLR提供以下类型的垃圾回收,可以基于工作负载的特征设置垃圾回收类型:

(1)工作站垃圾回收:为客户端应用设计;

回收发生在触发垃圾回收的用户线程上,并保留与用户线程相同的优先级(普通优先级),所以垃圾回收线程必须与其它线程竞争CPU时间。

(2)服务器垃圾回收:用于需要高吞吐量和可伸缩性的服务器应用程序

回收发生在以 THREAD_PRIORITY_HIGHEST 优先级运行的多个专用线程上,为每个CPU提供一个用于执行垃圾回收的一个堆和专用线程,多个垃圾回收线程一起工作。

3.4、垃圾回收通知(为担负大量请求的服务器应用准备)

服务器版本的CLR可以在完全垃圾回收之前发送通知。可以调用GC.RegisterForFullGCNotification启用通知。然后开启另一个线程持续监听GC.WaitForFullGCApproach(),当返回的GCNotificationStatus表示即将进行一次回收时,将工作负载重定向到另一个服务器实例,然后监听GC.WaitForFullGCComplete(),当该方法返回的状态表明回收完毕时在重新开始接受请求。

4、弱引用

CLR由System.WeakReference类实现弱引用,将Target属性设置为该对象。当垃圾收集器遇到一个弱引用指针指向对象时,不会将该对象加入引用关系图中。如果一个对象只有弱引用指向它,该对象不会被标记,垃圾回收时就可以清除此对象。

(1)短弱引用

垃圾回收回收对象后,弱引用的Target会变为null。弱引用本身时托管对象,也需要经过垃圾回收。

(2)长弱引用

在对象的Finalize方法调用后,长弱引用获得保留。这样便可以重新创建新对象。

若要建立强引用,可以将WeakReference的Target属性(前提是Target属性不为null,即对象未被回收)强制转换为对象类型。

c# .net core使用Hangfire组件来管理自动定时任务,连接的是redis服务,现在问题是占用内存太大

定时循环任务有2000个,5分钟运行一次,导致Hangfire的succeeded成功的记录越来越多,redis里面job新增的数量大于自动释放的数量,要怎么设置Hangfire自动释放成功记录,或者设置成功记录的释放时间短一点

参考技术A 搜一下:c#
.net
core使用Hangfire组件来管理自动定时任务,连接的是redis服务,现在问题是占用内存太大
参考技术B 查下程序里面,是不是有什么变量或者资源没有释放掉,造成了内存溢出了,这样运行得时间越长,越多的内存不会被释放掉,这样就占用的内存空间越多

以上是关于.Net 自动内存管理的主要内容,如果未能解决你的问题,请参考以下文章

使用自动内存管理

内存管理参考

cuda 功能的自动内存管理

内存自动管理

2.1 自动内存管理机制--Java内存区域与内存溢出异常

Objective-C内存管理之自动释放池