引用类型
值由三部分组成:
- 对象头
- 类型信息
- 字段内容
对象头
包含标志和同步块索引等数据
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会在必要时停止 其他处理线程
但不是直接停止(不能暂停其他线程),是让其他线程切换模式
托管线程模式
- 合作模式
- 抢占模式
GC工作模式
- 工作站模式:适用于内存占用量小的程序和桌面程序,可以提供更短的响应时间。
- 服务器模式:适用于内存占用量大的程序与服务程序,可以提供更高的吞吐量
| 不同点 | 工作站模式 | 服务器模式 |
|---|---|---|
| GC的频率 | 频繁 | 不频繁 |
| CC使用的线程 | 分配对象的线程 | 独立线程 |
| GC使用的线程数 | 单线程 | 多线程 |
各有各的优点,适用不同场景
GC类型
- 普通GC
- 后台GC
普通GC会导致更长的单次STW停顿时间,但消耗的资源比较小,并且支持压缩处理。
后台GC每次STW停顿时间会更短,但停顿次数与消耗的资源会更多,并且不支持压缩处理。
| 不同点 | 普通cC | 后台CC |
|---|---|---|
| 目标代 | 第0、1、2代 | 第2代 |
| 执行时间 | 短 | 长 |
| STW停顿时间 | 整个执行过程 | 部分执行过程 |
| 执行垃圾处理使用的线程 | 根据模式而定 | 独立线程 |
| 压缩处理 | 支持 | 不支持 |
.NET 程序的内存结构
标记,清除标记为0的对象?
垃圾回收机制会记录哪些对象存活
内存结构
托管函数代码堆:保存从托管函数由JIT编译过的机器代码
函数入口代码堆:保存未编译的
托管堆
一个进程(.NET程序)只有一个
用于保存引用类型对象的值
区域数量与工作模式,CPU核心(逻辑核心)有关
堆段:一个预先分配的固定大小的空间
引用类型对象 值 按顺序保存 堆段中
各个堆段通过链表理解在一起
小对象保存在小对象堆段,大对象保存在大对象堆段中
例外:一些不被回收或不大可能被回收的对象(不是大对象的)也会保存在大对象堆段中
如:字符串池分配的字符串对象,保存所有引用类型对象的数组,反射相关的对象
上面所述会保存在大对象堆中,会强制留在第二代,默认不参与压缩,这个操作有助于减少垃圾回收的工作量。
分配上下文
堆段如何分配对象值
以上在单线程下可以很好工作
多线程下需要获取线程锁(有性能影响)
因此,.NET运行时在线程获取线程锁时,会在堆段中分配一段空间,后续该线程可以使用这段空间不需要获取线程锁(直到空间用完,这个空间称为分配上下文)
分配上下文只适用小对象
短暂堆段
自由对象列表