.NET GC 全维度优化技术清单

一、弱引用体系

1. 基础弱引用

  1. WeakReference / WeakReference<T>
    • 短弱引用 (trackResurrection=false):推荐,Finalize 执行后直接失效;长弱引用极少用,容易复活泄漏
    • 用途:实现弱事件转发器、临时缓存、不阻碍 GC 的对象跟踪
  2. 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. 终结器避坑优化

  1. 终结器内不要访问其他托管对象(顺序不确定,可能已回收)
  2. 禁止在 Finalizer 中创建新对象、订阅事件
  3. 尽量少写终结器,优先手动 Dispose 释放资源

3. 事件手动解绑兜底

即使使用弱事件,ViewModel/Watcher 销毁时手动 RemoveHandler 主动解绑,减少 GC 扫描工作量,双重保险。

三、减少 GC 分配(最核心优化:少产生垃圾,GC 自然不用频繁回收)

1. 避免高频临时对象分配

  1. 值类型优先:小数据用 struct,不产生堆分配;避免频繁装箱拆箱
    • 例:变更路径参数、小型 ChangeArgs 结构体改用 struct,减少堆 GC
  2. 对象池 ObjectPool<T> 高频创建销毁的对象(变更参数、转发器、DTO)复用,彻底消除 GC 分配 csharp运行var pool = ObjectPool<ChangeArgs>.Create(); var args = pool.Get(); // 业务逻辑 pool.Return(args);
  3. 字符串优化
    • 高频拼接用 StringBuilder;固定字符串 string.Intern 驻留
    • 避免每次变更拼接长路径字符串(缓存路径、分段复用)
  4. 数组 / 集合复用 不要频繁 new List<T>、数组;提前扩容、Clear 复用,减少堆碎片

2. 避免闭包捕获强引用(高频内存泄漏源)

// 坏:lambda捕获this,事件源强持有Watcher,GC无法回收
model.PropertyChanged += (s,e)=> OnChange();

// 好:用弱事件转发器切断强引用链,就是我们自制WeakEventManager的核心价值
WeakEventManager.AddHandler(model, OnChange);

3. 禁用不必要的隐式装箱

反射、动态调用、object 传参极易装箱;能用泛型就不用 object,减少临时堆对象。

四、GC 手动控制 API(精细调控回收时机,谨慎使用)

  1. GC.SuppressFinalize(obj) 释放托管资源后调用,跳过终结队列,降低 GC 标记成本
  2. GC.ReRegisterForFinalize(obj) 极少用,资源恢复场景重新启用终结器
  3. GC.Collect(int generation, GCCollectionMode) 强制 GC 回收,仅用于批量释放大量对象后,业务主线程禁止频繁调用
    • 模式:GCCollectionMode.Optimized 按需回收;Forced 强制完整回收
  4. GC.WaitForPendingFinalizers() 等待终结器全部执行完成,配合 Collect 批量清理资源
  5. GC.TryStartNoGCRegion(long totalBytes) / GC.EndNoGCRegion()无 GC 区域:高频实时逻辑(上位机采集、工业监控)内禁止 GC 暂停,适合你的 Server 场景
    • 预先预留内存,代码块内不触发 GC 停顿,提升实时性

五、分代 GC 与内存布局优化(减少 Full GC)

.NET GC 分 0/1/2 代,0 代回收最快,2 代 Full GC 最慢(卡顿根源)

  1. 短期对象放 0 代:临时参数、局部变量,用完立刻失效,0 代快速回收
  2. 长期对象提升到老年代优化
    • 长期存活的模型根对象(Root 实体)一次性创建,不频繁重建,避免频繁晋升 2 代
    • 大对象 (>85000 字节) 直接进大对象堆 LOH,LOH 默认不压缩,容易碎片
  3. LOH 大对象堆优化
    • .NET4.6.2+:开启 LOH 压缩 AppContext.SetSwitch("System.GC.ConcurrentLargeObjectHeap", true)
    • 大数组、大集合使用对象池复用,避免频繁分配释放 LOH 碎片
  4. 并发 GC / 后台 GC 配置 xml<runtime> <gcConcurrent enabled="true"/> <!-- 后台GC,减少UI/业务卡顿 --> </runtime> 上位机、服务端程序必须开启,GC 回收时不阻塞工作线程

六、内存碎片优化(减少 GC 压缩开销)

  1. 避免频繁分配 / 释放大小差异巨大的对象,产生空洞碎片
  2. 连续批量创建同尺寸对象,方便 GC 合并空闲内存
  3. 长期运行服务定时批量释放临时资源,主动触发一次后台 GC 整理内存
  4. 结构体、小型类型栈分配,不占用堆内存

七、弱事件 / 多层模型场景专属 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 瓶颈)

  1. 运行时监控 API csharp运行GC.GetGeneration(obj); // 获取对象分代 GC.GetTotalMemory(false); // 当前堆内存
  2. 诊断工具
    • Visual Studio 内存探查器、GC 跟踪
    • PerfView:分析 GC 停顿、分配、FullGC 频率
    • dotnet-counters:命令行实时监控 GC 速率、代回收次数
  3. 日志埋点:记录 GC 回收次数、停顿时间,线上定位卡顿

九、高级:原生互操作 / 非托管内存 GC 优化

  1. System.Buffers.UnmanagedMemoryAllocator 非托管内存手动管理,不占用 GC 堆
  2. SafeHandle 封装非托管句柄,自动终结释放,避免内存泄漏
  3. 互操作 Marshal 分配的内存,必须手动 Free,GC 不会自动回收

十、核心避坑(极易造成 GC 压力 / 内存泄漏)

  1. 不要用静态集合(List/Dictionary/HashSet)缓存业务模型,强持有永久 GC 不掉,必须用 ConditionalWeakTable
  2. 事件强订阅不加弱转发器,ViewModel/Window 关闭后无法 GC,内存持续上涨
  3. 高频循环内 new class、字符串拼接,疯狂产生 0 代垃圾,频繁 GC 卡顿
  4. 大量大对象频繁创建,LOH 碎片堆积,触发耗时 Full GC
  5. Finalizer 内持有托管对象引用,对象无法回收,终结队列堵塞

按优先级总结落地顺序

  1. 基础:全部事件改用自制WeakEventManager弱订阅,切断强引用链
  2. 跟踪容器:HashSet → ConditionalWeakTable,消除对象强滞留
  3. 减少分配:变更参数 struct、高频对象 ObjectPool 复用、优化字符串拼接
  4. 资源释放:Watcher 实现 IDisposable,Dispose 主动解绑 + SuppressFinalize
  5. GC 配置:开启并发后台 GC,长服务启用 LOH 压缩
  6. 实时场景:高频采集逻辑套 NoGCRegion 减少停顿
  7. 监控:接入 GC 内存指标,定位频繁回收、FullGC 瓶颈

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注