.NET GC 全维度优化技术清单
一、弱引用体系
1. 基础弱引用
WeakReference/WeakReference<T>- 短弱引用 (trackResurrection=false):推荐,Finalize 执行后直接失效;长弱引用极少用,容易复活泄漏
- 用途:实现弱事件转发器、临时缓存、不阻碍 GC 的对象跟踪
SoftReference<T>(.NET5+) 软引用- GC 内存充足时保留对象,内存压力大自动回收;适合大图、大集合缓存
- 不适合事件监听,适合资源缓存层
2. CLR 内置弱附加容器(重点优化你 Watcher 的 HashSet)
ConditionalWeakTable<TKey,TValue>
- Key 是弱引用,Key 无其他引用时,条目自动被清理,不会像 HashSet 强持有对象造成 GC 滞留
- 你的场景优化:替换
HashSet<object> _trackedObjects记录已绑定模型,模型释放后自动清除绑定标记,无内存滞留 - 线程安全、无内存泄漏、性能极高,CLR 原生实现
3. 第三方弱集合
- 弱 Key 字典、弱 Value 缓存(如
WeakDictionary) - CommunityToolkit 弱事件监听
WeakEventListener:封装 WeakReference,自动管理事件订阅生命周期
二、终结器 (Finalizer)/IDisposable 资源释放优化(减少 GC 压力)
1. 标准释放模式(IDisposable + ~Finalizer)
public class Watcher : IDisposable
{
private bool _disposed;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); // 关键:告知GC不用执行终结器,减少一次GC扫描
}
protected virtual void Dispose(bool disposing)
{
if(_disposed) return;
if(disposing)
{
// 托管资源:解绑所有弱事件、清空跟踪表
}
// 非托管资源释放
_disposed = true;
}
~Watcher() => Dispose(false);
}
优化点:
GC.SuppressFinalize(this):跳过终结队列,减少 GC 标记阶段开销- 区分托管 / 非托管资源,非托管才保留终结器;纯托管对象直接删除~Finalizer
2. 终结器避坑优化
- 终结器内不要访问其他托管对象(顺序不确定,可能已回收)
- 禁止在 Finalizer 中创建新对象、订阅事件
- 尽量少写终结器,优先手动 Dispose 释放资源
3. 事件手动解绑兜底
即使使用弱事件,ViewModel/Watcher 销毁时手动 RemoveHandler 主动解绑,减少 GC 扫描工作量,双重保险。
三、减少 GC 分配(最核心优化:少产生垃圾,GC 自然不用频繁回收)
1. 避免高频临时对象分配
- 值类型优先:小数据用 struct,不产生堆分配;避免频繁装箱拆箱
- 例:变更路径参数、小型 ChangeArgs 结构体改用 struct,减少堆 GC
- 对象池 ObjectPool<T> 高频创建销毁的对象(变更参数、转发器、DTO)复用,彻底消除 GC 分配 csharp运行
var pool = ObjectPool<ChangeArgs>.Create(); var args = pool.Get(); // 业务逻辑 pool.Return(args); - 字符串优化
- 高频拼接用
StringBuilder;固定字符串string.Intern驻留 - 避免每次变更拼接长路径字符串(缓存路径、分段复用)
- 高频拼接用
- 数组 / 集合复用 不要频繁 new List<T>、数组;提前扩容、Clear 复用,减少堆碎片
2. 避免闭包捕获强引用(高频内存泄漏源)
// 坏:lambda捕获this,事件源强持有Watcher,GC无法回收
model.PropertyChanged += (s,e)=> OnChange();
// 好:用弱事件转发器切断强引用链,就是我们自制WeakEventManager的核心价值
WeakEventManager.AddHandler(model, OnChange);
3. 禁用不必要的隐式装箱
反射、动态调用、object 传参极易装箱;能用泛型就不用 object,减少临时堆对象。
四、GC 手动控制 API(精细调控回收时机,谨慎使用)
GC.SuppressFinalize(obj)释放托管资源后调用,跳过终结队列,降低 GC 标记成本GC.ReRegisterForFinalize(obj)极少用,资源恢复场景重新启用终结器GC.Collect(int generation, GCCollectionMode)强制 GC 回收,仅用于批量释放大量对象后,业务主线程禁止频繁调用- 模式:
GCCollectionMode.Optimized按需回收;Forced强制完整回收
- 模式:
GC.WaitForPendingFinalizers()等待终结器全部执行完成,配合 Collect 批量清理资源GC.TryStartNoGCRegion(long totalBytes)/GC.EndNoGCRegion()无 GC 区域:高频实时逻辑(上位机采集、工业监控)内禁止 GC 暂停,适合你的 Server 场景- 预先预留内存,代码块内不触发 GC 停顿,提升实时性
五、分代 GC 与内存布局优化(减少 Full GC)
.NET GC 分 0/1/2 代,0 代回收最快,2 代 Full GC 最慢(卡顿根源)
- 短期对象放 0 代:临时参数、局部变量,用完立刻失效,0 代快速回收
- 长期对象提升到老年代优化
- 长期存活的模型根对象(Root 实体)一次性创建,不频繁重建,避免频繁晋升 2 代
- 大对象 (>85000 字节) 直接进大对象堆 LOH,LOH 默认不压缩,容易碎片
- LOH 大对象堆优化
- .NET4.6.2+:开启 LOH 压缩
AppContext.SetSwitch("System.GC.ConcurrentLargeObjectHeap", true) - 大数组、大集合使用对象池复用,避免频繁分配释放 LOH 碎片
- .NET4.6.2+:开启 LOH 压缩
- 并发 GC / 后台 GC 配置 xml
<runtime> <gcConcurrent enabled="true"/> <!-- 后台GC,减少UI/业务卡顿 --> </runtime>上位机、服务端程序必须开启,GC 回收时不阻塞工作线程
六、内存碎片优化(减少 GC 压缩开销)
- 避免频繁分配 / 释放大小差异巨大的对象,产生空洞碎片
- 连续批量创建同尺寸对象,方便 GC 合并空闲内存
- 长期运行服务定时批量释放临时资源,主动触发一次后台 GC 整理内存
- 结构体、小型类型栈分配,不占用堆内存
七、弱事件 / 多层模型场景专属 GC 优化方案(贴合你的代码)
1. 跟踪容器替换
废弃 HashSet<object> → ConditionalWeakTable<object, bool>
- HashSet 强持有模型对象,模型无业务引用时依然卡在 2 代,无法 GC
- ConditionalWeakTable 弱持有,模型释放自动清除记录,无滞留
2. 弱事件代替原生 += 强订阅
- 切断 数据 Model → Watcher 的强引用链,Watcher 可正常 GC
- 配套:转发器内部使用
WeakReference<T>,不延长监听对象生命周期
3. 变更参数结构体化
把 ChangeArgs class 改为 readonly struct,完全消除堆分配,零 GC
public readonly struct ChangeArgs
{
public string FullPath { get; }
public object Source { get; }
public string PropertyName { get; }
public ChangeArgs(string path, object src, string prop)
{
FullPath = path;
Source = src;
PropertyName = prop;
}
}
4. 集合元素监听自动清理
集合元素移除时,无需手动解绑,弱转发器在 GC 时自动失效;配合 ConditionalWeakTable 自动清理绑定标记。
八、内存诊断 & GC 监控工具(定位 GC 瓶颈)
- 运行时监控 API csharp运行
GC.GetGeneration(obj); // 获取对象分代 GC.GetTotalMemory(false); // 当前堆内存 - 诊断工具
- Visual Studio 内存探查器、GC 跟踪
- PerfView:分析 GC 停顿、分配、FullGC 频率
- dotnet-counters:命令行实时监控 GC 速率、代回收次数
- 日志埋点:记录 GC 回收次数、停顿时间,线上定位卡顿
九、高级:原生互操作 / 非托管内存 GC 优化
System.Buffers.UnmanagedMemoryAllocator非托管内存手动管理,不占用 GC 堆SafeHandle封装非托管句柄,自动终结释放,避免内存泄漏- 互操作 Marshal 分配的内存,必须手动 Free,GC 不会自动回收
十、核心避坑(极易造成 GC 压力 / 内存泄漏)
- 不要用静态集合(List/Dictionary/HashSet)缓存业务模型,强持有永久 GC 不掉,必须用 ConditionalWeakTable
- 事件强订阅不加弱转发器,ViewModel/Window 关闭后无法 GC,内存持续上涨
- 高频循环内 new class、字符串拼接,疯狂产生 0 代垃圾,频繁 GC 卡顿
- 大量大对象频繁创建,LOH 碎片堆积,触发耗时 Full GC
- Finalizer 内持有托管对象引用,对象无法回收,终结队列堵塞
按优先级总结落地顺序
- 基础:全部事件改用自制
WeakEventManager弱订阅,切断强引用链 - 跟踪容器:HashSet → ConditionalWeakTable,消除对象强滞留
- 减少分配:变更参数 struct、高频对象 ObjectPool 复用、优化字符串拼接
- 资源释放:Watcher 实现 IDisposable,Dispose 主动解绑 + SuppressFinalize
- GC 配置:开启并发后台 GC,长服务启用 LOH 压缩
- 实时场景:高频采集逻辑套 NoGCRegion 减少停顿
- 监控:接入 GC 内存指标,定位频繁回收、FullGC 瓶颈