【C# .Net GC】开篇
前言
自从.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
系列文章
【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】开篇的更多相关文章
- 《InsideUE4》UObject(一)开篇
UE生UObject,UObject生万物 引言 在上个GamePlay专题,谈到UE创建游戏世界的时候(GamePlay架构(一)Actor和Component),简单的介绍了一下UObject的功 ...
- .NET面试题解析(00)-开篇来谈谈面试 & 系列文章索引
系列文章索引: .NET面试题解析(01)-值类型与引用类型 .NET面试题解析(02)-拆箱与装箱 .NET面试题解析(03)-string与字符操作 .NET面试题解析(04)-类型.方法与继承 ...
- .NET面试题解析(06)-GC与内存管理
系列文章目录地址: .NET面试题解析(00)-开篇来谈谈面试 & 系列文章索引 GC作为.NET的重要核心基础,是必须要了解的.本文主要侧重于GC内存管理中的一些关键点,如要要全面深入了 ...
- JVM基础系列第14讲:JVM参数之GC日志配置
说到 Java 虚拟机,不得不提的就是 Java 虚拟机的 GC(Garbage Collection)日志.而对于 GC 日志,我们不仅要学会看懂,而且要学会如何设置对应的 GC 日志参数.今天就让 ...
- JVM基础系列开篇:为什么要学虚拟机?
跟许多人一样,我一开始接触 Java 虚拟机只是因为面试需要用到,所以硬着头皮看看.所以很多人对于为什么要学虚拟机这个问题,他们的答案都是:因为面试.但我经过了几年的学习和实战,我发现其实学习虚拟机并 ...
- Android内存优化3 了解java GC 垃圾回收机制1
开篇废话 如果我们想要进行内存优化的工作,还是需要了解一下,但这一块的知识属于纯理论的,有可能看起来会有点枯燥,我尽量把这一篇的内容按照一定的逻辑来走一遍.首先,我们为什么要学习垃圾回收的机制,我大概 ...
- 一条数据的HBase之旅,简明HBase入门教程-开篇
常见的HBase新手问题: 什么样的数据适合用HBase来存储? 既然HBase也是一个数据库,能否用它将现有系统中昂贵的Oracle替换掉? 存放于HBase中的数据记录,为何不直接存放于HDFS之 ...
- 深入理解JVM虚拟机开篇:JVM介绍与知识脉络梳理
微信公众号[Java技术江湖]一位阿里 Java 工程师的技术小站.作者黄小斜,专注 Java 相关技术:SSM.SpringBoot.MySQL.分布式.中间件.集群.Linux.网络.多线程,偶尔 ...
- 一条数据的HBase之旅,简明HBase入门教程1:开篇
[摘要] 这是HBase入门系列的第1篇文章,主要介绍HBase当前的项目活跃度以及搜索引擎热度信息,以及一些概况信息,内容基于HBase 2.0 beta2版本.本系列文章既适用于HBase新手,也 ...
随机推荐
- TeXstudio在右边显示预览
打开预览界面后: 点击查看(View) 选择最后一个:窗口/内嵌(Windowed/Embedded) 就可以了
- gin中的SecureJSON 防止 json 劫持
使用 SecureJSON 防止 json 劫持.如果给定的结构是数组值或map,则默认预置 "while(1)," 到响应体. package main import ( &qu ...
- 返回值String表示视图
第一种:处理器方法返回String--表示逻辑视图名称(需配置视图解析器) 视图解析器: MyController类中: index.jsp中: 第二种:处理器方法方慧String,表示完整视图路径, ...
- python3 类的学习
# -*-coding:utf-8-*- # 定义类是通过class关键字,class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来 ...
- (2)puppet单机测试命令apply
单机测试apply命令: 以独立的方式,将清单中的配置应用于本机,也就是说,根据配置清单配置当前服务器. 1.apply这个子命令有很多选项,而我们常用的有debug.verbose.noop等,de ...
- 源码分析axios(1)~源码分析、模拟axios的创建
■ 查看源码发现,起初axios[instance=bind(Axios.prototype.request, context);]是一个函数, 但后续[ utils.extend(instance, ...
- Asp-Net-Core开发笔记:接口返回json对象出现套娃递归问题
前言 看了下推送记录,一个月前,OK,我又变成月更了o(╯□╰)o,这绝对不行![○・`Д´・ ○] 所以今天来更新了 其实不是我懒得更新或者是太忙,其实是最近在写一篇很长的博客,一直没写完( Ĭ ^ ...
- 字的研究(3)fontTools-TrueType轮廓坐标的获取以及基于TrueType的Glyph实例的构建
前言 本文主要介绍如果使用Python第三方库fontTools提取OpenType字体文件中的TrueType轮廓坐标以及如何构建基于TrueType的Glyph实例 TrueType轮廓坐标的获取 ...
- 关于Linux安装中NAT模式和桥接模式的区别详解(转载)
1.一般我们在创建一个Linux虚拟机时候,会面临三个网络配置选择: 桥接模式.nat模式.host-only模式(主机模式,这个模式用得少,就不介绍了) 2.NAT模式: 所谓nat模式,就是虚拟系 ...
- Vue.js的指令、生命周期钩子与数据选项
vue.js官网:https://cn.vuejs.org/v2/guide/components-registration.html 一.常用指令 v-if ... v-else: 作用:控制元素是 ...