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

代码中如何检测对象内存占用?为何各内存统计数值有差异?

.NET内存评估数值差异与代码问题解析

问题描述

我使用Diagnostics评估内存中对象大小,发现三个数值存在明显差异:

  • GC.GetTotalMemory()返回值:22236
  • 遍历堆中所有对象求和值:452999
  • 任务管理器显示的进程内存占用:11.4MB
    疑惑在于对象总和大于总内存统计值,想明确真实内存占用,追踪自有对象的内存消耗,询问代码错误点。

测试代码

private static void EvaluateMemoryConsumption()
{
    GC.Collect(); 
    GC.WaitForPendingFinalizers(); 
    GC.Collect();

    long totalMemory = GC.GetTotalMemory(forceFullCollection: false);
    long totalObjSize = 0;

    using (var dataTarget = DataTarget.AttachToProcess(Process.GetCurrentProcess().Id, suspend: false))
    {
        using (var runtime = dataTarget.ClrVersions[0].CreateRuntime())
        {
            foreach (var segment in runtime.Heap.Segments)
            {
                foreach (var obj in segment.EnumerateObjects())
                {
                   totalObjSize += (long)obj.Size;                          
                }
            }
        }
    }

    Console.WriteLine($"TotalSize: {totalMemory}, ObjectSize: {totalObjSize}");   
    
    // TotalSize: 22236, ObjectSize: 452999
}

数值差异的核心原因

1. GC.GetTotalMemory()的统计逻辑

GC.GetTotalMemory(forceFullCollection: false)仅统计托管堆中存活对象的已分配内存,不包含:

  • 托管堆的空闲预留区域(GC预先向操作系统申请的未使用内存块)
  • CLR内部管理的额外开销(如对象头、同步块、GC元数据)
  • 非托管内存(如Diagnostics附加进程时的非托管开销)
    且参数forceFullCollection: false不会强制触发完整GC,可能遗漏部分待回收垃圾,导致统计值偏低。

2. Diagnostics堆遍历的统计逻辑

runtime.Heap.Segments.EnumerateObjects()会遍历整个托管堆的所有对象,包括:

  • 已标记为垃圾但尚未被GC回收的对象
  • CLR自带的系统对象(线程上下文、GC内部结构、Diagnostics附加时生成的临时对象)
    这些非业务对象会大幅拉高统计值,且obj.Size包含对象的全部内存(包括对象头等CLR管理部分)。

3. 任务管理器的统计逻辑

任务管理器显示的是进程私有工作集,涵盖进程占用的所有内存:

  • 托管堆全部内存(已分配+空闲预留)
  • 非托管内存(DLL加载、系统调用、Diagnostics附加开销)
  • 线程栈、代码段、映射文件等,范围最广,因此数值最大。

代码中的错误点

  1. GC.GetTotalMemory参数设置错误:forceFullCollection: false未触发完整GC,统计的存活对象内存不准确。
  2. 未过滤非业务对象:遍历所有堆对象,包含大量CLR系统对象,导致统计值虚高。
  3. 自身进程附加的干扰:Diagnostics附加当前进程时,会在进程内创建额外对象和内存开销,被计入统计值。

修正方案

1. 修正GC.GetTotalMemory调用

设置forceFullCollection: true,确保统计存活对象的准确内存:

long totalMemory = GC.GetTotalMemory(forceFullCollection: true);

2. 过滤自有业务对象

遍历堆对象时,仅统计自身程序集内的对象:

foreach (var obj in segment.EnumerateObjects())
{
    var type = obj.Type;
    if (type != null && type.Assembly.GetName().Name.Equals("YourAssemblyName", StringComparison.Ordinal)) // 替换为你的程序集名称
    {
        totalObjSize += (long)obj.Size;
    }
}

3. 消除附加进程的干扰

将统计逻辑移至独立进程中,避免Diagnostics附加对目标进程内存的影响;或使用更轻量的方式追踪自有对象,比如通过类型标记、内存快照对比。

4. 明确不同统计值的适用场景

  • 追踪自有对象内存:使用过滤后的Diagnostics统计值,或结合GC.GetTotalMemory(true)减去系统对象基准值
  • 进程实际内存占用:参考任务管理器的私有工作集(包含非托管部分)
  • 托管堆已使用内存:使用GC.GetTotalMemory(true)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 10:17:36