目前项目开发基本都基于.NetCore 3.1以上了,有些老版本的规则和概念也没有列出来,低版本的垃圾回收类型和内存释放方式会有所不同
为优化垃圾回收器的性能,将托管堆分为三代:第 0 代、第 1 代和第 2 代。目的是为了单独处理短生存期对象和长生存期对象。垃圾回收器大部分时间都在处理短生存期对象的回收。
底层一代的GC回收会触发年轻一代的GC回收,第二代的GC回收会触发完整的GC回收.
第0代(暂时代) | 第1代(暂时代) | 第2代 | LOH(逻辑第3代) | |
---|---|---|---|---|
所处内存段 | 暂时段 | 暂时段 | 非暂时段 | 非暂时段LOH(大型对象堆)实际位于第二代单独在第二代上为其划分了一块区域。逻辑上称为第3代 |
包含 | 短生存期对象,即新分配的对象 | 短生存期对象,从第0代回收后,未被回收的对象升级为第1代。 | 长生存期的对象,第一代回收后,未被回收的对象升级为第2代。 | 对象的大小>= 85,000 字节 |
回收条件 | 第0代已分配内存达到阈值如果第0代已满,仍尝试创建新对象 调用GC.Collect()方法 第1代GC回收 | 第1代已分配内存达到阈值 第0代回收之后仍然没有足够的空间存放新对象(此时会先回收第1代,再回收第2代) 调用GC.Collect方法第2代GC回收 | 第2代已分配内存达到阈值 第0代回收之后仍然没有足够的空间存放新对象(此时会先回收第1代,再回收第2代) 调用GC.Collect方法 达到LOH回收条件 系统内存不足 | 达到第2代回收条件 大型对象内存分配达到阈值 |
回收方式 | 前台垃圾回收,当前托管线程被挂起 | 前台垃圾回收,当前托管线程被挂起 | 后台垃圾回收,当前托管线程正常执行 | 同第二代 |
想要判断一个对象是否为大对象,可通过以下代码查看
var o = new Byte[85000];
Console.WriteLine(GC.GetGeneration(o));//GC2,大对象
o = new Byte[84900];
Console.WriteLine(GC.GetGeneration(o)); //GC0,小对象 84999仍是大对象,需要用一定量的内存空间保存指针
var arr = new int[85000 / 4];
Console.WriteLine(GC.GetGeneration(arr));//GC2,大对象,数组会提前开辟空间, int占32位,4个字节,85000 / 4加上指针内容会达到大对象的大小
arr = new int[85000 / 4 - 20];
Console.WriteLine(GC.GetGeneration(arr));//GC0,小对象
当垃圾回收器检测到某个代中的幸存率很高时,它会增加该代的分配阈值,避免垃圾回收过于频繁地运行
但是阈值调大之后,会导致一次回收的内存过高。
所以阈值由CLR动态决定,以调节 回收频率和单次回收内存大小的平衡
工作站(默认方式) | 服务器 | |
---|---|---|
特点 | 垃圾回收线程同用户线程优先级相同,会与用户线程争用CPU资源只有一个处理器的计算机无论是否修改配置文件最终都会应用工作站垃圾回收方式 | 有垃圾回收的专用线程线程优先级为THREAD_PRIORITY_HIGHEST每个CPU都会分配一个垃圾回收专用线程和专用堆。不同的堆可以互通多个垃圾回收线程一起工作,所以堆大小相同时,服务器垃圾回收比工作站垃圾回收快 |
适用场景 | 普通场景 | 需要高吞吐量和可伸缩性的服务器应用程序 |
GC释放应用程序不再使用的对象的内存,通过检查应用程序的根来确定不再使用的对象
应用程序的根包括:静态字段、局部变量、CPU 寄存器、GC 句柄和终结队列
- 列出不可访问对象和幸存对象的地址块并**标记**
- 使用内存复制功能压缩可以访问的对象到不可访问的地址块中,就是把存活下来的对象重新排列到连续的内存块中
- 大对象通常不会压缩,因为大对象所占用的内存区域过大,移动成本太大
- 回收死空间
- 指针更正,让对象指针指向新地址,指针更正是因为压缩了对象,对象在内存中的位置发生了变化
Microsoft.Diagnostics.Tracing.TraceEvent
nuget包在代码中监听指定的GC回收等事件自定义后续处理逻辑