You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

获取GC压缩托管堆数组时的通知需求及相关疑问

关于.NET GC压缩通知及内存偏移的问题解答

一、GC压缩的通知机制

目前.NET公共API没有直接提供GC开始压缩和压缩完成的精准通知事件,但可以通过两种方式间接监控相关行为:

  • 使用GC.RegisterForFullGCNotification、GC.WaitForFullGCApproach、GC.WaitForFullGCComplete这组API,能感知全量GC的趋近与完成状态。不过这类API仅针对全量GC,无法精准区分压缩阶段的起止——GC执行流程是标记->整理(压缩)->清理,它们只能监控整个全量GC周期的边界。
  • 借助.NET CLR Memory分类下的性能计数器(如% Time in GC、Gen 2 Collections)间接判断GC是否发生,但同样无法定位压缩阶段的具体节点。

如果你的算法严重依赖内存偏移稳定性,更稳妥的方案是避免直接依赖托管堆内存偏移:

  • 改用值类型(struct)数组,值类型直接存储在数组连续内存块中,GC压缩时整个数组会被整体移动,但内部元素的相对偏移始终不变。
  • 若必须使用引用类型,可将关键数据存入非托管内存(如Marshal.AllocHGlobal分配的内存),这部分内存不受GC压缩影响。

二、代码示例中的内存偏移变化问题

针对你给出的代码:

class Test {
  public int A;
  public double B;
  public string C;
}

Test[] tests = new Test[20]; 

ref char first = ref Unsafe.AsRef(tests[0].C[0]);
ref char next = ref Unsafe.AsRef(tests[1].C[0]);

nint diff = Unsafe.ByteOffset(ref first, ref next);

明确以下两点:

  1. diff值在GC压缩时大概率会变化:
    • Test是引用类型,数组tests存储的是指向Test实例的引用,而非实例本身。每个Test实例在托管堆中独立分配,GC压缩时会按需移动这些实例的位置;同时string的字符数据也存储在托管堆独立内存块中,同样可能被GC移动。因此first和next指向的内存地址都可能被修改,二者的字节偏移diff自然会发生变化。
  2. GC会对这类结构进行压缩:
    • 只要Test实例、string字符数据位于GC的压缩代(通常是第2代/老年代),且GC执行整理型回收时,就会被压缩移动。

注意:直接使用Unsafe.ByteOffset获取托管堆中不同对象的内存偏移属于高危操作,GC压缩行为会随时破坏偏移的有效性,可能导致程序崩溃或数据损坏。

内容的提问来源于stack exchange,提问作者Shpendicus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 01:10:27