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

56

我正在开发一个应用程序,目前已经创建了成百上千个对象。

是否有可能确定(或者近似估计)对象(类实例)所分配的内存?


1
如果只有几百,你为什么要担心它呢? - AnthonyWJones
我指的是类的一个实例,而不是对象类。 - FerranB
4
@FerranB - 我认为Anthony的意思是,假设这些实例是相当典型的对象而不是大型文档或数组,分配数百个实例并不是什么大问题。除非你是指的是“数千万”而不是“数百个”,否则你可能不需要担心。 - Greg Beech
简单的实例没有问题。但是带有 DataTables、DataGrids 的实例会有影响吗? - FerranB
@AnthonyWJones,这取决于每个对象的大小...Windows win = new Windows(); - JaredBroad
10个回答

42

40
一种简单的方法是,如果您想知道特定对象正在发生什么,可以这样做。
// 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;

希望这能有所帮助;)

11

10

7
你还可以使用WinDbg和SOS或SOSEX(类似于SOS但具有更多命令和一些现有命令的改进)WinDbg扩展程序。要分析特定内存地址处的对象,您需要使用的命令是!objsize。
非常重要的一点是,!objsize仅提供类本身的大小,并不一定包括包含在类中的聚合对象的大小 - 我不知道为什么它不这样做,因为有时这会非常令人沮丧和误导性。
我在Connect网站上创建了两个功能建议,要求将此功能包含在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提供的答案中的一些信息:

  1. OP询问的是“对象”的大小,而不是“类”。对象是类的一个实例。也许这就是你的意思?
  2. 为对象分配的内存不包括JIT编译的代码。JIT代码位于其自己的“JIT代码堆”中。
  3. JIT仅按方法逐个编译代码,而不是按类级别编译。因此,如果从未为类调用某个方法,则永远不会对其进行JIT编译,因此在JIT代码堆上永远不会为其分配内存。

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

  1. 加载器堆:包含CLR结构和类型系统
  2. 高频堆:静态、MethodTables、FieldDescs、接口映射
  3. 低频堆:EEClass、ClassLoader和查找表
  4. 存根堆:CAS、COM包装器、P/Invoke的存根
  5. 大对象堆:需要超过85k字节的内存分配
  6. GC堆:应用程序私有的用户分配堆内存
  7. JIT代码堆:由mscoreee(执行引擎)和JIT编译器为托管代码分配的内存
  8. 进程/基础堆:Interop/非托管分配,本机内存等

希望对您有所帮助


7
每个“类”都需要足够的内存来容纳所有已由运行时调用的成员的jit编译代码(尽管如果您很长时间不调用方法,CLR可以释放该内存并在再次调用时重新jit它...),以及足够的内存来容纳在类中声明的所有静态变量...但是这种内存仅分配一次,无论您创建多少类的实例。
对于您创建的每个类的实例(未被垃圾回收),您可以通过将每个基于实例声明的变量(字段)的内存使用情况相加来近似计算内存占用量...
引用变量(指向其他对象的引用)占用4或8字节(32/64位操作系统?) int16、Int32、Int64分别占用2、4或8个字节...
字符串变量需要额外的存储空间来存储一些元数据元素(加上地址指针的大小)
此外,对象中的每个引用变量也可以被认为“间接”包括它所指向的堆上的对象所占用的内存,尽管您可能希望将该内存视为属于该对象而不是引用它的变量...
等等。

1
我可以近似地创建自己的类,但不能近似其他的(例如 .net 控件)。 - FerranB
字符串比指针占用更多的内存。我认为我测量了大约18个字节。 - Rauhotz
这里有几个需要澄清的地方。我已经修改了我的答案以帮助澄清/纠正你的解释。 - Dave Black

6

要了解应用程序中的内存分配情况,可以在WinDbg中使用以下sos命令。

!dumpheap -stat

请注意,!dumpheap 只会给出对象类型本身的字节,并不包括它可能引用的任何其他对象类型的字节。
如果您想查看特定对象类型所引用的所有对象的总字节数(即将您的对象引用的所有对象的字节数相加),请使用类似 dot Trace 的内存分析器 - http://www.jetbrains.com/profiler/

实例上的 !objsize 也很有用。 - Brian Rasmussen

5
如果可以的话 - 进行序列化!
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

6
序列化并不能帮助确定对象的运行时内存成本。可以重新计算的字段不需要被序列化,例如。再者,各种格式化程序不会从一开始就进行内存转储,因此仅仅是衡量了对象序列化后的大小,而非在内存中的大小。 - Joey
@Joey:至少,你可以比较两个对象或同一对象的状态。 - serhio
这里应该使用内存分析器来解决问题。你可以检查运行时和虚拟机,为什么要猜测呢? - Joey

1

这里有一个学术问题:对象在运行时的大小是多少? 这很有趣,但只有连接到正在运行的进程的性能分析器才能正确回答。我最近花了很长时间研究这个问题,并确定没有通用方法既准确又快速,以至于您永远不想在生产系统中使用它。像数字类型数组之类的简单情况有简单的答案,但除此之外,最好的答案是不要费心去计算。为什么你想知道这个?是否有其他可用的信息可以达到相同的目的?

在我的情况下,我最终想回答这个问题,是因为我有各种有用的数据,但可以丢弃以释放RAM以供更重要的服务使用。这里的代表是撤销堆栈缓存

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


0

对于任何正在寻找答案的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}");
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接