前言

自从.NET Core 3.0开始对根据自己具体的应用场景去配置GC ,让GC 发挥最好的作用。
.NET 5 改动更大,而且.NET 5整体性能比.net core 3.1高20%,并且在GC这块.NET 5开放了更多配置,所以.NET 5很值得关注。

GC管理你服务的内存分配和释放,GC在运行公共语言运行时(CLR Common Language Runtime)中,GC可以帮助开发人员有效的分配内存和和释放内存,大多数情况下是不需要去担心的,但是有时候服务总是是出现莫名的问题,所以还是有必要了解一下GC的基础知识的。

通过学习GC工作原理,学会手动调优。能够根据程序的执行情况,做出一个符合程序运行的GC运行策略,让应用程序更加高效、稳定。

例如: 启用Server GC 对于高吞吐量的程序有帮助, 禁用 Concurrent GC 实际上对一个高密度计算的程序是有性能提升的。

GC系列的知识点参考:microsoft垃圾回收的基本知识、CLR via C# 托管堆和垃圾回收

 相关联的类:

GCSettings 类
GCLatencyMode 枚举

GCKind 枚举
GCLargeObjectHeapCompactionMode 枚举
System.GC
GCCollectionMode

GCMemoryInfo

系列文章

【C# .Net GC】内存管理

【C# .Net GC】内存分配原则

【C# .Net GC】垃圾回收算法

【C# .Net GC】GC的工作模式与工作方式

【C# .Net GC】延迟模式 通过API-GC调优

【C# .Net GC】条件自动垃圾回收 HandleCollector类

【C# .Net GC】强制垃圾回收 和System GC

【C# .Net GC】清除非托管类型(Finalize终结器、dispose模式以及safeHandler)

【C# .Net GC】GC初始化设置 和GcSetting

基础知识

GC 管理内存配合和回收

公共语言运行时的垃圾回收器为应用程序管理内存的分配和释放。

GC内存分配原则

暂时代(0代+1代):因为第 0 代和第 1 代中的对象的生存期较短,因此,这些代被称为“暂时代”。

暂时段:暂时代在称为“暂时段(段:Segment)”的内存段中进行分配。 垃圾回收器获取的每个新段将成为新的暂时段,并包含在第 0 代垃圾回收中幸存的对象。 旧的暂时段将成为新的第 2 代段。

第 2 代段:保存第二代对象(大对象 以及暂时代的幸存对象)的内存段。

根据系统为 32 位还是 64 位以及它正在哪种类型的垃圾回收器(工作站或服务器 GC)上运行,暂时段的大小发生相应变化。 下表显示了暂时段的默认大小。

Segment的大小取决于系统是32位还是64位,以及它正在运行的垃圾收集器的类型,下表列出了分配时系统所使用的默认值:

GC 类型 32-bit 64-bit
Workstation(工作站) GC 16 MB 256 MB
Server GC(服务器) 64 MB 4 GB
Server GC with > 4 logical(逻辑) CPUs 32 MB 2 GB
Server GC with > 8 logical(逻辑) CPUs 16 MB 1 GB
 

暂时段可以包含第 2 代对象。 第 2 代对象可使用多个段(在内存允许的情况下进程所需的任意数量)。

从暂时垃圾回收中释放的内存量限制为暂时段的大小。 释放的内存量与死对象占用的空间成比例。

WorkStation GC 和 Server GC区别

  1、Server GC 的 Generation 内存更大,64位操作系统 Generation 0 的大小居然有4G ,这意味着啥?在不调用GC.Collect 的情况下,4G 塞满GC 才会去回收。那样性能可是有很大的提升。但是一旦回收了,4GB 的“垃圾” 也够GC喝一壶的了。

 2、Server GC 拥有专门用来处理 GC的线程,而WorkStation GC 的处理线程就是你的应用程序线程。WorkStation 形式下,GC 开始,所有应用程序线程挂起,GC选择最后一个应用程序线程用来跑GC,直到GC 完成。所有线程恢复。而ServerGC 形式下: 有几核CPU ,那么就有几个专有的线程来处理 GC。每个线程都一个堆进行GC ,不同的堆的对象可以相互引用。所以在GC 的过程中,Server GC 比 WorkStation GC 更快。有专有线程,但并不代表可以并行GC哦。

  上面两个区别,决定了 Server GC 用于对付高吞吐量的程序,而WorkStation GC 用于一般的客户端程序足以。

GC调优指标是什么?

暂停时间是GC很重要的一个指标,意思是在GC暂停多长时间才能执行其它工作(常说的"卡顿" 或 "挂起线程"),暂停时间较长就会直接影响程序工作延迟。。。(如果是大型项目至于会造成什么影响,脑补下。。。)

根是什么??

应用程序的根包含线程堆栈上的静态字段、局部变量、CPU 寄存器、GC 句柄表中的项(clr via C#P483)和终结列表(clr via C#P479)。

分代回收器

加载 CLR 时,GC 分配两个初始堆段:一个用于小型对象(小型对象堆或 SOH),一个用于大型对象(大型对象堆,每个对象都大于85000字节)。

用户代码只能在第 0 代(小型对象)或第2代 LOH(大型对象)中分配。从本质上讲,第 1 代是新对象区域与生存期较长的对象区域之间的缓冲区。

第 0 代:小型对象始终在第 0 代中进行分配,或者根据它们的生存期,可能会提升为第 1 代或第 2 代。

第 1 代:执行第 1 代 GC 时,将同时回收第 1 代和第 0 代。

第 2 代:大型对象始终在第 2 代中进行分配。大型对象属于第 2 代,因为只有在第 2 代回收期间才能回收它们。执行第 2 代 GC 时,将回收整个堆。

GC的工作模式主要有两种;

工作模式是针对进程的,程序启动后就不能修改了。只能在配置文件.json .xml进行设置。但是可用通过GCSeting类的GCLatencyMode进行微调。

  • 工作站(默认的.NET程序都是WorkStation GC)
  • 服务器 (服务器 GC 是服务器垃圾回收的默认模式)

GC的工作方式主要有两种

每种GC类型都对应两种工作方式

  • 后台(.net4.0后用后台模式取代并发模式,而且只适用于第2代收集)
  • 非并发

工作方式 选项只影响第 2 代中的垃圾回收;第 0 代和第 1 代中的垃圾回收始终是非并发的,因为它们完成的速度很快。后台垃圾收集是在一个或多个专用线程上执行的,这取决于它是工作站还是服务器GC,并且只适用于第2代收集。默认情况下启用后台垃圾回收。它可以通过.net Framework应用程序中的gcConcurrent配置设置或.net Core和.net 5及更高版本应用程序中的System.GC.Concurrent设置来启用或禁用。

后台垃圾回收期间对暂时代的回收称为“前台”垃圾回收。。当发生前台垃圾收集时,所有托管线程都被挂起。

当后台垃圾收集正在进行,并且在第0代中已经分配了足够多的对象时,CLR 将执行第 0 代或第 1 代前台垃圾回收。专用的后台垃圾收集线程经常在安全点检查,以确定是否有前台垃圾收集的请求。如果有,后台收集将挂起自己,以便进行前台垃圾收集。前台垃圾收集完成后,专用的后台垃圾收集线程和用户线程恢复。

后台垃圾回收可以消除并发垃圾回收所带来的分配限制,因为在后台垃圾回收期间,可发生暂时垃圾回收。 后台垃圾回收可以删除暂存世代中的死对象。 如果需要,它还可以在第 1 代垃圾回收期间扩展堆。

后台服务器垃圾回收与后台工作站垃圾回收具有类似功能,但有一些不同之处:

  • 后台工作区域垃圾回收使用一个专用的后台垃圾回收线程,而后台服务器垃圾回收使用多个线程。 通常一个逻辑处理器有一个专用线程。

  • 不同于工作站后台垃圾回收线程,这些后台服务器 GC 线程不会超时

托管堆是什么?.

托管堆:CLR要求所有对象都从托管堆中分配。进程初始化时,CLR划出一个地址空间区域作为托管堆。

(CLR还要维护一个指针,我们称它作NextObjPtr。该指针指向下一个对象在堆中的分配位置)

1.调用IL指令newobj,为代表资源的类型分配内存(一般使用C#new操作符来完成)
2.访问类型的成员来使用资源(有必要可以重复)
3.摧毁资源的状态以进行清理
4.释放内存。垃圾回收器独自负责这一步

C#的new操作符导致CLR执行以下步骤

1.计算类型的字段(以及从基类型继承的字段)所需的字节数
2.加上对象开销所需的字节数。每个对象都有两个开销字段:类型对象指针和同步块索引。
   32位应用程序,这两个字段各自需要32位,所以每个对象要增加8字节
   64位应用程序,这两个字段各自需要64位,所以每个对象要增加16字节
3.CLR检查区域中是否有分配对象所需的字节数。如果托管堆有足够的可用空间,就在NextObjPtr指针指向的地址处放入对象,为对象分配的字节会被清零。接着调用类型的构造器(为this参数传递NextObjPtr),new操作符返回对象引用。就在返回这个引用之前,NextObjPtr指针的值会加上对象占用的字节数来得到一个新值,及下个对象放入托管堆时的地址

触发垃圾回收的因素

当满足以下条件之一时将发生垃圾回收:

  • 操作系统报告低内存请看(将触发第2代垃圾回收)。 这是通过 OS 的内存不足通知或主机指示的内存不足检测出来。

  • 由托管堆上已分配的对象使用的内存超出了可接受的阈值。 随着进程的运行,此阈值会不断地进行调整。触发第0代回收

  • 调用 GC.Collect 方法。 几乎在所有情况下,你都不必调用此方法,因为垃圾回收器会持续运行。 此方法主要用于特殊情况和测试。如果应用程序代码通过调用 GC.Collect 方法并将 generation 参数指定为 2 来包含回收。

  • 应用程序调用new操作符创建对象,发现没有足够的地址空间来分配对象,CLR就经行垃圾回收。
  • CLR卸载APPDomain,所有代0、1、2的垃圾回收。
  • CLR正在关闭,收回内存

提升性能

CLR的GC是基于代的垃圾回收器(generational garbage collector),它对你的代码做出了以下几种假设。

  • 对象越新,生存期越短
  • 对象越老,生存期越大
  • 回收堆的一部分,速度快于整个堆

第2代的产生的过程:GC对第0代对象执行一个完整的GC算法后产生第一代。对新产生的的第0代对象执行算法又产生第一代,将新产生的第一代和原先的第一代放在一起。如此反复多次后,将产生大量的第一代对象,直到用完了第一代的预算内存。

新一轮垃圾回收时候,GC将检查第0代和第1代的所有对象,此次垃圾回收后将产生第1代和第2代幸存对象。托管堆只支持0、1、2代回收。

完整 的过程请看CLR第4版P454- P456。

注释:

(1)CLR初始化时,会为每一代选择预算。然而,CLR的垃圾回收器是自调节的。这意味着垃圾回收器会在执行垃圾回收的过程中了解应用程序的行为。

(2)如果垃圾回收器发现在回收0代后存活下来的对象很少,就可能减少第0代的预算。已分配空间的减少意味着垃圾回收将更频繁的发生。

(3)另一个方面,如果垃圾回收器收了第0代,发现还有很多对象的话,没有多少内存被回收就会增大第0代的预算。现在,垃圾回收的次数将减少,但每次进行垃圾回收时,回收的内存要多得多。如果没有回收到足够的内存,垃圾回收器会执行一次完整的回收

如果还是不够,就抛出OutOfMemoryException异常。

大对象和小对象

CLR将对象分为大对象和小对象。本章到目前为止说的都是小对象。目前认为85000字节或更大的对象是大对象。

CLR以不同方式对待大小对象:

  • CLR检测第0代超过预算时候触发一次GC
  • 大对象不是在小对象的地址空间分配,而是在进程地址空间的其他地方分配。
  • 目前版本的GC不压缩大对象,因为在内存中移动它们代价过高。但这可能在进程中的大对象之间造成地址空间的碎片化,以至于抛出OutOMemoryException.CLR将来的版本可能压缩大对象。
  • 大对象总是第2代,绝不可能是第0代或第1代。所以只能为需要长时间存活的资源创建大对象。分配短时间存活的大对象会导致第2代被更频繁地回收,会损害性能。大对象一般是大字符串(比如XML或JSON)或者用于10操作的字节数组(比如从文件或网络将字节读入缓冲区以便处理)。

按需压缩大对象堆

即使使用了对象池,仍然可能会在大对象堆里分配对象,随着时间的推移,在里面会存在很多碎片。从.NET 4.5.1 开始,你可以告诉GC在下一次做完整GC时顺便也对LOH做一次压缩。

GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;

根据LOH的大小,这个压缩过程可能会很慢,甚至会用到好几秒。你最好是在你的程序能够长时间暂停的时候,才让垃圾回收器做一次这样的完整GC。修改该设置值,只会在下一次完整GC时会触发压缩,一旦完成了LOH的压缩,GCSettings.LargeObjectHeapCompactionMode就会被重新设置为GCLargeObjectHeapCompactionMode.Default。

因为这个过程很耗时,我还是建议你减少对LOH的分配或者使用对象池。这样将大大减少压缩的数据。压缩LOH功能只能作为碎片过多,分配的堆太大时的最后手段。

垃圾回收事件

GCNotification

最新文章

  1. thinkphp3.2.3在框架截取文字
  2. Gradle中的buildScript代码块
  3. select2插件的使用
  4. 将Cocos2dX渲染到MFC窗口上
  5. css写一个梯形
  6. 一:java概述:
  7. 调试中除了在URL上加时间戳外,如何避免js、css被返回304状态?
  8. Spring Boot(spring mvc升级版)
  9. ElasticSearch+Kibana 索引操作
  10. 无废话WCF入门教程五[WCF的通信模式]
  11. Nginx keepalived实现高可用负载均衡详细配置步骤
  12. HDU - 1789 贪心
  13. Spring Boot 2.x 编写 RESTful API (五) 单元测试
  14. boost库在windows上的安装
  15. .NET垃圾回收机制(一)
  16. 关于python的多行注释,启动新浏览器,循环语句乘法口诀
  17. rsync安装及部署
  18. Go指南练习_循环与函数
  19. Sublime_text编辑器插件安装
  20. 微信小程序 - wxpage

热门文章

  1. 【转载】ASP.NET前台代码绑定
  2. 513. Find Bottom Left Tree Value
  3. javascript的AMD规法--esl与requirejs浅介。
  4. golang中json格式化自定义日期格式
  5. LNMP架构搭建
  6. netty系列之:选byte还是选message?这是一个问题
  7. Win10正式版屏幕一直闪烁怎么办
  8. ApacheJemeter的简单使用
  9. ubuntu php连接oracle数据库
  10. Javascript 生成全局唯一标识符 (GUID,UUID)