【CLR C#】浅谈.Net的GC(垃圾回收)机制及其整体流程

简介: 在.NET程序开发中,为了将开发人员从繁琐的内存管理中解脱出来,将更多的精力花费在业务逻辑上,CLR提供了自动执行垃圾回收的机制来进行内存管理,开发人员甚至感觉不到这一过程的存在。.NET程序可以找出某个时间点上哪些已分配的内存空间没有被程序使用,并自动释放它们。自动找出并释放不再使用的内存空间机制,就称为垃圾回收机制。本文主要介绍.Net中的GC(垃圾回收)机制及其整体流程。
写在前面:

在.NET程序开发中,为了将开发人员从繁琐的内存管理中解脱出来将更多的精力花费在业务逻辑上,CLR提供了自动执行垃圾回收的机制来进行内存管理,开发人员甚至感觉不到这一过程的存在。.NET程序可以找出某个时间点上哪些已分配的内存空间没有被程序使用,并自动释放它们。自动找出并释放不再使用的内存空间机制,就称为垃圾回收机制。本文主要介绍.Net中的GC(垃圾回收)机制及其整体流程


本文关键字:CLR、.Net、GC(垃圾回收)、C#、面试

一、定义

CLR执行垃圾回收的过程,有以下几点:

  • 如何判断哪些对象是可以进行回收的,哪些是要保留的?
  • 对象在堆上是如何分布的?何时执行垃圾回收?
  • 垃圾回收的过程如何进行的?有哪些优化策略?

1. 什么是GC

.NET程序可以找出某个时间点上哪些已分配的内存空间没有被程序使用,并自动释放它们。

自动找出并释放不再使用的内存空间机制,就称为垃圾回收机制(Garbage Collection,简称GC)。

2. 栈空间和堆空间

为数据申请内存空间的操作称为分配,释放与申请内存空间的操作被称为释放。

每个线程都有独立的栈空间,栈空间用于保存调用函数的数据,堆空间是程序中一块独立的空间,从堆空间分配的数据可以被程序中的所有函数和线程访问,并且不会随函数返回与线程结束释放。

3.值类型和引用类型

值类型的对象本身存储值,而引用类型的对象本身存储内存地址,值存储在内存地址指向的空间中。

值类型与引用类型的对象本身存储在栈空间还是堆空间是根据定义的位置而定的。

值类型的对象会根据定义的位置隐式分配与释放。

引用类型的对象需要通过new关键字显示分配,new会从堆空间申请一块空间用于保存值,然后返回空间的开始地址。

二、.NET 中的 GC

垃圾回收机制的主要工作是找出堆空间分配的空间中哪些空间不再被程序使用,然后回收这些空间。在.NET中使用的方式,是最主流的"标记并清除"的方式。.NET中的根对象,包括各个线程栈空间上的变量,全局变量、GC句柄和析构队列中的对象。

标记.png

1. 分代

.NET中将引用类型的对象分为三类,分别是第0代、第1代与第2代。

第0代中的对象存活时间通常最短,第1代中的对象存活时间比较长,第2代中的对象存活时间最长。

分代依据的目的是,尽量增加每次执行垃圾回收处理时,可回收的对象的数量,并减少处理所需的时间

分代算法.png

2. 压缩

反复执行分配与回收操作,可能导致堆上产生很多空余空间,这些空余空间又被称为碎片空间。

压缩机制可以通过移动已分配空间把碎片空间合并到一块,使得堆可以分配更大的对象。

.NET运行时提供的GC是支持压缩机制的,但是只在一定的条件下启用。

3. 大小对象

.NET根据引用类型对象值占用的空间大小来区分是小对象还是大对象。

大对象与小对象会在不同的堆区域中分配:大对象堆和小对象堆。

移动大对象需要的成本很高,前面我们提到的压缩机制默认只在小对象堆启用,大对象堆是不会执行压缩的。

4. 固定对象

托管代码传递引用类型对象给非托管代码时必须创建固定类型的GC句柄,并在托管代码中保持这个句柄存活到非托管代码的调用结束。

创建了固定类型GC句柄的对象就称为固定对象。

使用固定对象会带来一些副作用,那就是由固定对象带来的碎片空间是无法合并的。

5. 析构队列

如果在垃圾回收的过程中执行这些析构函数,垃圾回收需要的时间是不可预料的。

如果对象不再存活但定义了析构函数,那么对象会添加到析构队列并标记存活。

析构函数执行完毕的对象,可以在下一轮GC中被回收

析构函数如下:

public class Class : IDisposable
    {
        public int ClassId { get; set; }
        public string ClassName { get; set; }
        ~Class()
        {
            MyLog.Log($"执行{this.GetType().Name}Dispose");
        }
        public void Dispose()
        {
            MyLog.Log($"执行{this.GetType().Name}Dispose");
        }
    }

6.STW

对象之间的引用关系会随着程序运行不断改变,让执行GC的线程与执行其他处理的线程同时运行会带来一些问题。

STW.png

让执行GC处理以外的线程全都暂停运行,像这样的停止操作我们称为STW(Stop The World)。

三、工作站模式与服务器模式

工作站模式,适用于内存占用量小的程序和桌面程序,它可以提供更短的响应时间。
服务器模式适用于内存占用量大的程序与服务程序,可以提供更高的吞吐量。

工作站模式与服务器模式.png

四、普通GC与后台GC

普通GC会导致更长的单次STW停顿时间,但消耗的资源比较小,并且支持压缩处理。
后台GC每次STW停顿时间会更短,但停顿次数与消耗的资源会更多,并且不支持压缩处理。

普通GC与后台GC.png

五、引用类型的数据结构

引用类型对象的值由三个部分组成,分别是对象头类型信息各个字段的内容

1. 对象头

对象头包含了标志与同步块索引等数据。

高1位用于.NET运行中内部检查托管堆状态时,标记对象是否已检查。

高2位用于标记是否抑制运行对象的析构函数。

高3位用于标记对象是否为固定对象。

高4、5、6为用于标记低26位保存了什么内容,其中就包括了获取锁、释放锁和对象Hash值的信息。

2. 类型信息

类型信息是一个指向的是.NET运行时内部保存的类型数据(MethodTable)的内存地址。

类型数据包含了类型的所属模块名称、字段列表、属性列表、方法列表,以及各个方法的入口点的地址等信息。

六、.NET 程序的内存结构

程序的内存结构.png

1. 托管堆和堆段

托管堆用于保存引用类型对象的值;

托管堆和堆段.png

每个堆段默认的大小同样根据GC模式与运行环境的CPU逻辑核心数量来决定

GC模式 CPU逻辑核心数 32位 64位
工作站模式 —— 16MB 256MB
服务器模式 <4 64MB 4GB
服务器模式 >=4&&<8 32MB 2GB
服务器模式 >=8 16MB 1GB

2. 分配上下文

分配上下文1.png

分配上下文2.png

在.NET中,托管堆每个区域的小对象堆有三个代,大对象堆有一个代(第2代),这些代会通过generation类型的实例进行管理。
代的开始地址决定了哪些对象在哪些代?

分代的实现.png

3. 自由对象列表

自由对象列表1.png

如果自由空间出现在以分配空间的尾部,那么它会释放给操作系统,并且所占空间会归为未分配空间。

自由对象列表2.png

堆段上自由对象所占的空间可以称为碎片空间。
托管堆的每个区域有4个自由对象列表,它们分别记录,第0代的自由对象、第1代的自由对象、第2代小对象堆段的自由对象、第2代大对象堆段的自由对象。
为了提升从用自由对象的效率,第2代的自由对象列表,还会根据自由对象的大小进行分组。

4. 跨代引用记录

.NET 实现分代的主要原因是为了支持垃圾回收时只处理一部分对象。

.NET 中有一个数组专门记录跨代引用,这个数组又称为卡片表,卡片表会标记所有发生夸代引用的位置。

卡片束记录卡片表中哪些位置有标记,先扫描卡片束再扫描卡片表就可以减少处理时间。

5. 析构对象与析构队列

析构对象与析构队列.png

七、GC的总体流程

1. GC的触发

流程.png

第1个条件是 分配对象时找不到可用空间

第2个条件是分配量超过阈值

第3个条件是托管代码主动调用GC.Cllect函数

第4个条件是收到物理内存不足的通知

2. 分配对象时找不到可用空间

第1种是针对第1代的GC,这一种GC会尝试回收短暂堆段上的对象,使得短暂堆段有更多空间。

第2种是针对第2代的GC,也就是完整GC,这种GC会在物理内存不足或执行第1种GC以后仍然无法分配时触发。

3. 分配量超过阈值

如果在某个代分配的对象值大小合计超过分配量域值,就会触发针对这个代的GC。

存活下来的对象越多,新分配量阈值越高。

4. GC.Collect

托管代码中调用GC.Collect函数可以主动触发GC,这个函数最多可以接收4个参数。

public static void Collect(
    int generation,
    GCCollectionMode mode,
    bool blocking,
    bool compacting
);

5. 物理内存不足

物理内存接近用尽时,操作系统会把物理内存中的部分内容移动到分页文件。

.NET为了避免因使用分页文件带来的性能低下,会自动检测物理内存是否接近不足,然后触发GC。


写在结尾:

文章中出现的任何错误请大家批评指出,一定及时修改。

希望看到这里的小伙伴能给个三连支持!

相关文章
|
9月前
|
存储 算法 Java
G1原理—5.G1垃圾回收过程之Mixed GC
本文介绍了G1的Mixed GC垃圾回收过程,包括并发标记算法详解、三色标记法如何解决错标漏标问题、SATB如何解决错标漏标问题、Mixed GC的过程、选择CollectSet的算法
G1原理—5.G1垃圾回收过程之Mixed GC
|
9月前
|
存储 算法 Java
G1原理—6.G1垃圾回收过程之Full GC
本文详细探讨了G1垃圾回收器对Full GC(FGC)的优化处理,涵盖FGC的前置处理、整体流程及并行化改进。重点分析了传统FGC串行化的局限性以及G1通过Region分区和RSet机制实现并行标记的优势,包括任务窃取提升效率、跨分区压缩以生成空闲Region等技术细节。此外,文章还介绍了G1的新特性——字符串去重优化,通过判断char数组一致性减少重复字符串占用内存,从而提升内存使用效率。总结部分全面回顾了G1在FGC中的各项优化措施及其带来的性能改善。
G1原理—6.G1垃圾回收过程之Full GC
|
9月前
|
存储 算法 Java
G1原理—4.G1垃圾回收的过程之Young GC
本文详细解析了G1垃圾回收器中YGC(Young Generation Collection)的完整流程,包括并行与串行处理阶段。内容涵盖YGC相关参数设置、YGC与Mixed GC及FGC的关系、新生代垃圾回收的具体步骤(如标记存活对象、复制到Survivor区、动态调整Region数量等),以及并行阶段的多线程操作和串行阶段的关键任务(如处理软引用、整理卡表、重构RSet)。
G1原理—4.G1垃圾回收的过程之Young GC
|
8月前
|
SQL 小程序 API
如何运用C#.NET技术快速开发一套掌上医院系统?
本方案基于C#.NET技术快速构建掌上医院系统,结合模块化开发理念与医院信息化需求。核心功能涵盖用户端的预约挂号、在线问诊、报告查询等,以及管理端的排班管理和数据统计。采用.NET Core Web API与uni-app实现前后端分离,支持跨平台小程序开发。数据库选用SQL Server 2012,并通过读写分离与索引优化提升性能。部署方案包括Windows Server与负载均衡设计,确保高可用性。同时针对API差异、数据库老化及高并发等问题制定应对措施,保障系统稳定运行。推荐使用Postman、Redgate等工具辅助开发,提升效率与质量。
325 0
|
12月前
|
开发框架 搜索推荐 算法
一个包含了 50+ C#/.NET编程技巧实战练习教程
一个包含了 50+ C#/.NET编程技巧实战练习教程
358 18
|
12月前
|
缓存 算法 安全
精选10款C#/.NET开发必备类库(含使用教程),工作效率提升利器!
精选10款C#/.NET开发必备类库(含使用教程),工作效率提升利器!
404 12
|
12月前
|
开发框架 人工智能 .NET
C#/.NET/.NET Core拾遗补漏合集(24年12月更新)
C#/.NET/.NET Core拾遗补漏合集(24年12月更新)
188 6
|
12月前
|
开发框架 算法 .NET
C#/.NET/.NET Core技术前沿周刊 | 第 15 期(2024年11.25-11.30)
C#/.NET/.NET Core技术前沿周刊 | 第 15 期(2024年11.25-11.30)
204 6
|
12月前
|
开发框架 Cloud Native .NET
C#/.NET/.NET Core技术前沿周刊 | 第 16 期(2024年12.01-12.08)
C#/.NET/.NET Core技术前沿周刊 | 第 16 期(2024年12.01-12.08)
204 6
|
12月前
|
算法 网络协议 Java
【JVM】——GC垃圾回收机制(图解通俗易懂)
GC垃圾回收,标识出垃圾(计数机制、可达性分析)内存释放机制(标记清除、复制算法、标记整理、分代回收)