.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代的收集,当垃圾回收器检测到某个代中的幸存率很高时,会增加该代的分配阈值。回收一代时同时回收它前面的所有代。
图2-1 堆上的各代内存(引用C#7.0核心技术指南)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,即对象未被回收)强制转换为对象类型。
网友评论