C#/.NET 对象使用多少内存?

Posted

技术标签:

【中文标题】C#/.NET 对象使用多少内存?【英文标题】:How much memory does a C#/.NET object use? 【发布时间】:2010-09-30 09:30:27 【问题描述】:

我正在开发一个当前创建了数百个对象的应用程序。

是否可以确定(或近似)对象(类实例)分配的内存?

【问题讨论】:

如果只有几百个,你为什么要担心呢? 我指的是一个类的实例,而不是对象类。 @FerranB - 我认为 Anthony 的意思是分配数百个实例并不是什么大问题,假设它们是相当典型的对象而不是大量文档或数组。除非您指的是“数千万”而不是“数百”,否则您可能无需担心。 简单实例没问题。但是有 DataTables 的实例,DataGrids 没关系? @AnthonyWJones,取决于每个对象的大小...Windows win = new Windows(); 【参考方案1】:

您可以使用类似的内存分析器

.NET 内存分析器 (http://memprofiler.com/)

CLR Profiler(免费)(http://clrprofiler.codeplex.com/)

【讨论】:

【参考方案2】:

如果您想知道特定对象发生了什么情况,可能是一种粗略的方法

// Measure starting point memory use
GC_MemoryStart = System.GC.GetTotalMemory(true);

// Allocate a new byte array of 20000 elements (about 20000 bytes)
MyByteArray = new byte[20000];

// Obtain measurements after creating the new byte[]
GC_MemoryEnd = System.GC.GetTotalMemory(true);

// Ensure that the Array stays in memory and doesn't get optimized away
GC.KeepAlive(MyByteArray);

进程范围的东西可能像这样获得

long Process_MemoryStart = 0;
Process MyProcess = System.Diagnostics.Process.GetCurrentProcess();
Process_MemoryStart = MyProcess.PrivateMemorySize64;

希望这会有所帮助;)

【讨论】:

【参考方案3】:

ANTS memory profiler 将准确告诉您为每个对象/方法/等分配了多少。

【讨论】:

【参考方案4】:

Here's a related post 我们讨论了确定引用类型的大小。

【讨论】:

【参考方案5】:

您还可以使用 WinDbg 和 SOS 或 SOSEX(例如带有更多命令和改进了一些现有命令的 SOS)WinDbg 扩展。用于分析特定内存地址的对象的命令是 !objsize

要记住的一个非常重要的项目是 !objsize 只给你类本身的大小,并不一定包括类中包含的聚合对象的大小 - 我不知道为什么它不这样做有时这很令人沮丧和误导。

我在 Connect 网站上创建了 2 个功能建议,要求将此功能包含在 VisualStudio 中。请投票给您希望添加的项目!

https://connect.microsoft.com/VisualStudio/feedback/details/637373/add-feature-to-debugger-to-view-an-objects-memory-footprint-usage

https://connect.microsoft.com/VisualStudio/feedback/details/637376/add-feature-to-debugger-to-view-an-objects-rooted-references

编辑: 我添加以下内容以澄清 Charles Bretana 提供的答案中的一些信息:

    OP 询问“对象”而不是“类”的大小。对象是类的一个实例。也许这就是你的意思? 为对象分配的内存不包括 JITted 代码。 JIT 代码存在于自己的“JIT 代码堆”中。 JIT 仅在逐个方法的基础上编译代码,而不是在类级别。因此,如果一个方法永远不会被某个类调用,那么它就永远不会被 JIT 编译,因此永远不会在 JIT 代码堆上为其分配内存。

顺便说一句,CLR 使用大约 8 种不同的堆:

    加载程序堆:包含 CLR 结构和类型系统 高频堆:静态、MethodTables、FieldDesc、接口映射 低频堆:EEClass、ClassLoader 和查找表 存根堆:用于 CAS、COM 包装器、P/Invoke 的存根 大型对象堆:需要超过 85k 字节的内存分配 GC 堆:用户分配的应用专用堆内存 JIT 代码堆:由 mscoreee(执行引擎)和托管代码的 JIT 编译器分配的内存 进程/基堆:互操作/非托管分配、本机内存等

HTH

【讨论】:

【参考方案6】:

要大致了解应用程序中的内存分配,请在 WinDbg 中使用以下 sos 命令

!dumpheap -stat

请注意,!dumpheap 只为您提供对象类型本身的字节,不包括它可能引用的任何其他对象类型的字节。

如果您想查看特定对象类型的总持有字节数(对象引用的所有对象的所有字节的总和),请使用内存分析器,如 dot Trace - http://www.jetbrains.com/profiler/

【讨论】:

!objsize 对实例也有用【参考方案7】:

每个“类”都需要足够的内存来保存运行时调用的所有成员的所有 jit 编译代码,(尽管如果您在相当长的一段时间内不调用方法,CLR 可以释放如果您再次调用该内存并再次对其进行重新处理...加上足够的内存来保存类中声明的所有静态变量...但是无论您有多少类实例,这个内存每个类只分配一次创建。

对于您创建的类的每个实例(并且尚未被垃圾收集),您可以通过将每个基于实例的声明变量的内存使用量相加来近似内存占用量...(字段)

引用变量(引用其他对象)占用 4 或 8 个字节(32/64 位操作系统?) int16, Int32, Int64 分别占 2,4, 或 8 个字节...

字符串变量为一些元数据元素占用额外的存储空间,(加上地址指针的大小)

此外,对象中的每个引用变量也可以被认为是“间接”包括它指向的对象在堆上占用的内存,尽管您可能希望将该内存计为属于该对象而不是引用它的变量...

等等。等等

【讨论】:

我可以近似自己的类,但不能近似其他类(例如 .net 控件) 字符串比指针占用更多的内存。我想我测量了大约 18 个字节。 这里有几点需要澄清。我已经修改了上面的答案,以帮助澄清/纠正您的解释。【参考方案8】:

如果可以的话 - 序列化它!

Dim myObjectSize As Long

Dim ms As New IO.MemoryStream
Dim bf As New Runtime.Serialization.Formatters.Binary.BinaryFormatter()
bf.Serialize(ms, myObject)
myObjectSize = ms.Position

【讨论】:

序列化无助于确定对象的运行时内存成本。例如,可以重新计算的字段不需要序列化。然后各种格式化程序开始时不会进行内存转储,因此它只是衡量对象在序列化时的大小,而不是在内存中。 @Joey:至少,你可以比较两个对象,或者相同的对象状态 内存分析器应该是这里的答案。您可以检查运行时和虚拟机,所以为什么要猜测?【参考方案9】:

有一个学术问题运行时对象的大小是多少?这很有趣,但只能由分析器正确回答,即附加到正在运行的进程。我最近花了很长时间研究这个问题,并确定没有足够准确和快速的通用方法让您想在生产系统中使用它。像数值类型数组这样的简单案例有简单的答案,但除此之外,最好的答案是 不要费心去解决它。你为什么想知道这个?是否有其他信息可以达到同样的目的?

在我的情况下,我最终想要回答这个问题,因为我有各种有用的数据,但可以丢弃以释放 RAM 用于更关键的服务。这里的典型代表是 Undo StackCache

最终我得出结论,管理撤消堆栈和缓存大小的正确方法是查询可用内存量(这是一个 64 位进程,因此可以安全地假设它全部可用),然后如果有足够大的 RAM 缓冲区,则允许添加更多项目,如果 RAM 运行不足,则需要删除项目。

【讨论】:

【参考方案10】:

对于任何潜伏在寻找答案的 Unity 开发人员,这里有一种方法可以比较受@varun 答案启发的两个不同的类内存分配:

void Start()
    
        var totalMemory = System.GC.GetTotalMemory(false);
        
        var class1 = new Class1[100000];
        System.GC.KeepAlive(class1);
        for (int i = 0; i < 100000; i++)
        
            class1[i] = new Class1();
        

        var newTotalMemory = System.GC.GetTotalMemory(false);
        Debug.Log($"Class1: newTotalMemory - totalMemory = newTotalMemory - totalMemory");

        var class2 = new Class2[100000];
        System.GC.KeepAlive(class2);
        for (int i = 0; i < 100000; i++)
        
            class2[i] = new Class2(10, 10);
        

        var newTotalMemory2 = System.GC.GetTotalMemory(false);
        Debug.Log($"Class2: newTotalMemory2 - newTotalMemory = newTotalMemory2 - newTotalMemory");
    

【讨论】:

以上是关于C#/.NET 对象使用多少内存?的主要内容,如果未能解决你的问题,请参考以下文章

C#.NET对象深拷贝

如何在 .Net 2.0/C# 中将 StreamReader 转换为 XMLReader 对象

pdb 文件包含多少信息? (C#/.NET)

C#.net如何手动释放内存资源

.Net 自动内存管理

如何在 .NET 中实现共享内存?