CLR-GC-2

133 阅读6分钟

引用类型

值由三部分组成:

  1. 对象头
  2. 类型信息
  3. 字段内容

对象头

包含标志和同步块索引等数据

32位平台:4个字节

64位:8个字节(只有后面四个字节会被用到)

(4字节)32位中

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

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

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

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

类型信息

类型信息 是一个指向的

.NET 运行时内部保存的类型数据(MethodTable)的内存地址。

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

固定对象

托管代码(Managed Code)

  • 定义 :托管代码是由.NET运行时(CLR,Common Language Runtime)环境管理和执行的代码。这种代码使用.NET语言(如C#、VB.NET、F#等)编写,并在编译后生成中间语言(IL,Intermediate Language)代码,然后在运行时由CLR转换成本地机器代码。

非托管代码(Unmanaged Code)

  • 定义 :非托管代码是不依赖于CLR运行时的代码,通常由本地编译器生成,并且没有CLR的垃圾回收和内存管理功能。这种代码直接操作计算机硬件和操作系统资源。

.NET 支持托管代码调用非托管代码

一个引用类型对象传递给非托管代码,其内存地址会被复制到非托管代码管理的区域中

.NET 无法知道内存保存在哪里,无法知道非托管代码是否还在使用该对象

如果进行压缩操作,非托管代码中保存的引用类型对象地址就不能同步更新

.NET就要求在这个过程中必须创建固定类型的GC句柄,并在托管代码中保持这个句柄存活 非托管代码的调用结束

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

压缩操作会避开这些对象,会产生垃圾碎片

固定对象在垃圾回收后可能会降代

析构队列

.NET 支持在回收对象前调用析构函数(其中可调用非托管代码)

析构函数的执行需要时间不确定 -> 垃圾回收时间不可预料

则需要一个析构队列和一个析构线程

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

GC结束后启动一个析构线程,该线程会从析构队列中取对象执行析构函数,执行完毕的对象可在下一轮GC中被回收。

析构函数通常是在使用非托管类型的对象中定义(不绝对)

如FileStream(使用了文件句柄)

使用Dispose,关闭文件句柄

这些对象在我们不使用托管资源时应该主动去调用Dispose释放,执行后应抑制析构函数的运行?

STW

GC线程和其他处理线程的冲突

让GC处理以外的线程全部暂停运行,这样的操作称为STW (Stop The World)

如何减少STW时间

.NET 的GC会在必要时停止 其他处理线程

但不是直接停止(不能暂停其他线程),是让其他线程切换模式

托管线程模式

  1. 合作模式
  2. 抢占模式

GC工作模式

  1. 工作站模式:适用于内存占用量小的程序和桌面程序,可以提供更短的响应时间。
  2. 服务器模式:适用于内存占用量大的程序与服务程序,可以提供更高的吞吐量
不同点工作站模式服务器模式
GC的频率频繁不频繁
CC使用的线程分配对象的线程独立线程
GC使用的线程数单线程多线程

各有各的优点,适用不同场景

GC类型

  1. 普通GC
  2. 后台GC

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

后台GC每次STW停顿时间会更短,但停顿次数与消耗的资源会更多,并且不支持压缩处理。

不同点普通cC后台CC
目标代第0、1、2代第2代
执行时间
STW停顿时间整个执行过程部分执行过程
执行垃圾处理使用的线程根据模式而定独立线程
压缩处理支持不支持

.NET 程序的内存结构

标记,清除标记为0的对象?

垃圾回收机制会记录哪些对象存活

内存结构

1701133797766.png

托管函数代码堆:保存从托管函数由JIT编译过的机器代码

函数入口代码堆:保存未编译的

托管堆

一个进程(.NET程序)只有一个

用于保存引用类型对象的值

1701134278045.png

区域数量与工作模式,CPU核心(逻辑核心)有关

堆段:一个预先分配的固定大小的空间

引用类型对象 值 按顺序保存 堆段中

各个堆段通过链表理解在一起

小对象保存在小对象堆段,大对象保存在大对象堆段中

例外:一些不被回收或不大可能被回收的对象(不是大对象的)也会保存在大对象堆段中

如:字符串池分配的字符串对象,保存所有引用类型对象的数组,反射相关的对象

上面所述会保存在大对象堆中,会强制留在第二代,默认不参与压缩,这个操作有助于减少垃圾回收的工作量。

分配上下文

堆段如何分配对象值

1701135045450.png

以上在单线程下可以很好工作

多线程下需要获取线程锁(有性能影响)

1701135156733.png

因此,.NET运行时在线程获取线程锁时,会在堆段中分配一段空间,后续该线程可以使用这段空间不需要获取线程锁(直到空间用完,这个空间称为分配上下文)

分配上下文只适用小对象

短暂堆段

自由对象列表