前言

自从.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

【C# .Net GC】开篇的更多相关文章

  1. 《InsideUE4》UObject(一)开篇

    UE生UObject,UObject生万物 引言 在上个GamePlay专题,谈到UE创建游戏世界的时候(GamePlay架构(一)Actor和Component),简单的介绍了一下UObject的功 ...

  2. .NET面试题解析(00)-开篇来谈谈面试 & 系列文章索引

    系列文章索引: .NET面试题解析(01)-值类型与引用类型 .NET面试题解析(02)-拆箱与装箱 .NET面试题解析(03)-string与字符操作 .NET面试题解析(04)-类型.方法与继承 ...

  3. .NET面试题解析(06)-GC与内存管理

      系列文章目录地址: .NET面试题解析(00)-开篇来谈谈面试 & 系列文章索引 GC作为.NET的重要核心基础,是必须要了解的.本文主要侧重于GC内存管理中的一些关键点,如要要全面深入了 ...

  4. JVM基础系列第14讲:JVM参数之GC日志配置

    说到 Java 虚拟机,不得不提的就是 Java 虚拟机的 GC(Garbage Collection)日志.而对于 GC 日志,我们不仅要学会看懂,而且要学会如何设置对应的 GC 日志参数.今天就让 ...

  5. JVM基础系列开篇:为什么要学虚拟机?

    跟许多人一样,我一开始接触 Java 虚拟机只是因为面试需要用到,所以硬着头皮看看.所以很多人对于为什么要学虚拟机这个问题,他们的答案都是:因为面试.但我经过了几年的学习和实战,我发现其实学习虚拟机并 ...

  6. Android内存优化3 了解java GC 垃圾回收机制1

    开篇废话 如果我们想要进行内存优化的工作,还是需要了解一下,但这一块的知识属于纯理论的,有可能看起来会有点枯燥,我尽量把这一篇的内容按照一定的逻辑来走一遍.首先,我们为什么要学习垃圾回收的机制,我大概 ...

  7. 一条数据的HBase之旅,简明HBase入门教程-开篇

    常见的HBase新手问题: 什么样的数据适合用HBase来存储? 既然HBase也是一个数据库,能否用它将现有系统中昂贵的Oracle替换掉? 存放于HBase中的数据记录,为何不直接存放于HDFS之 ...

  8. 深入理解JVM虚拟机开篇:JVM介绍与知识脉络梳理

    微信公众号[Java技术江湖]一位阿里 Java 工程师的技术小站.作者黄小斜,专注 Java 相关技术:SSM.SpringBoot.MySQL.分布式.中间件.集群.Linux.网络.多线程,偶尔 ...

  9. 一条数据的HBase之旅,简明HBase入门教程1:开篇

    [摘要] 这是HBase入门系列的第1篇文章,主要介绍HBase当前的项目活跃度以及搜索引擎热度信息,以及一些概况信息,内容基于HBase 2.0 beta2版本.本系列文章既适用于HBase新手,也 ...

随机推荐

  1. [一]Cesium利其器——Visual Studio Code

    Cesium中文网:http://cesiumcn.org/ | 国内快速访问:http://cesium.coinidea.com/ IDE Web前端刚入门的朋友,常常想选择一个快速.好用.流行( ...

  2. HTTP状态码1XX深入理解

    前段时间看了<御赐小仵作>,里面有很多细节很有心.看了一些评论都是:终于在剧里能够看到真正在搞事业.发了工资第一时间还钱的正常人了.我印象比较深的是王府才能吃上的葡萄.觉得非常合理.剧里说 ...

  3. JVM专题3: GC 垃圾回收

    合集目录 JVM专题3: GC 垃圾回收 什么是GC? 为什么要有 GC? Garbage Collection, 用于内存回收. 简述一下 Java 垃圾回收机制? 那些内存需要回收 虚拟机中程序计 ...

  4. django之mysqlclient安装

    如果运行环境中没有安装mysqlclient,在迁移数据库时会发生错误 一.在windows下安装: ·如果直接使用 pip install mysqlclient 会提示安装失败(版本不对或者找不到 ...

  5. 写程序时try,catch查看报错的行号

    try {    ////////////////    代码段   //////////////// }catch(Exception ex) {     MessageBox.Show(ex.St ...

  6. C语言非阻塞式键盘监听

    监听键盘可以使用C语言的字符输入函数,例如 getchar.getch.getche 等,使用getche函数监听键盘的例子: #include <stdio.h> #include &l ...

  7. js表达式和语句

    表达式 一个表达式可以产生一个值,有可能是运算.函数调用.有可能是字面量.表达式可以放在任何需要值的地方. 语句 语句可以理解为一个行为,循环语句和判断语句就是典型的语句.一个程序有很多个语句组成,一 ...

  8. ajax、axios、fetch区别及优缺点

    将jQuery的ajax.axios和fetch做个简单的比较,所谓仁者见仁智者见智,最终使用哪个还是自行斟酌 1.jQuery ajax $.ajax({ type: 'POST', url: ur ...

  9. Java内存分析简单介绍

    原创:转载需注明原创地址 https://www.cnblogs.com/fanerwei222/p/11904422.html Java内存分析简单介绍: 1. # 设置内存溢出时自动生成堆内存快照 ...

  10. swift语言学习博文精选

    初探swift语言的学习笔记九(OC与Swift混编) Objective-C 与 Swift 混编之路 Swift项目兼容Objective-c问题汇总