C# 能否获取一个对象所占内存的大小

本文探讨了如何确定C#对象在内存中占用的大小。提到非静态字段和属性影响对象大小,以及sizeof运算符在值类型上的应用。由于引用类型无法直接使用sizeof,作者转而使用Marshal.SizeOf方法。实验表明,对于托管对象无法直接获取内存大小,非托管对象则可以通过Marshal.SizeOf。此外,尝试使用二进制序列化方法也被证明不可行。

  今日,在项目重构的时候忽然想到一个问题,一个类哪些成员的增加,会影响一个类所占内存的大小?C#有没有办法知道一个对象占多少内存呢?

     第一个问题:很快想到是类的非静态的字段、属性。

     第二个问题:首先想到的是sizeof()。

下面开始验证,首先来验证值类型,验证代码如下:

int size = sizeof (int); //4个字节

注意点:sizeof 运算符仅适用于值类型,而不适用于引用类型。sizeof 运算符只能在不安全代码块中使用。如下面的代码将无法编译通过:

public struct TestStuct
    {

    }

int size = sizeof(new TestStuct());

编译后,提示:

错误 1 “ConsoleApplication3.TestStuct”没有预定义的大小,因此 sizeof 只能在不安全的上下文中使用(请考虑使用 System.Runtime.InteropServices.Marshal.SizeOf) 

修改为Marshal.SizeOf方法,改方法返回对象的非托管大小(以字节为单位)。参数可以是引用类型或装箱的值类型。布局必须是连续的或显式的。

int size = Marshal.SizeOf(new TestStuct()); //1个字节

接下来来验证引用类型:

由于不能作为非托管结构进行封送处理;无法计算有意义的大小或偏移量。所有下面的代码在运行的时候,会抛出异常。

 public class Student
    {
    }

 int size = Marshal.SizeOf(new Student());

需要给Student类,加上一个StructLayoutAttribute,来控制Student类的数据字段的物理布局。修改代码为:

 [StructLayout(LayoutKind.Sequential)]
    public class Student
    {
    }

int size = Marshal.SizeOf(new Student()); //1个字节

LayoutKind 默认值为Auto.

结论:
1:对于托管对象是没有办法直接获取到一个对象所占的内存大小。
2:非托管对象,可以使用
Marshal.SizeOf
3:对内置类型,如int,long,byte等使用sizeof

扩展:
有人提出使用二进制序列化,将一个对象序列化成一个MemoryStream,然后返回MemoryStream.Length,经过验证是不可以的。

验证代码如下:

复制代码
[Serializable]
 public class Student
    {
    }

private static long GetObjectSize(object o)
        {
            using (var stream = new MemoryStream())
            {
                var formatter = new BinaryFormatter();
                formatter.Serialize(stream, o);
                using (var fileStream = new FileStream(@"D:\Student.txt", FileMode.OpenOrCreate, FileAccess.Write))
                {
                    var buffer = stream.ToArray();
                    fileStream.Write(buffer, 0, buffer.Length);
                    fileStream.Flush();
                }

                return stream.Length;
            }
        }

 var student = new Student();
 long size = GetObjectSize(student);  //139个字节
复制代码

Student.txt保存的文本信息如下所示,通过文本信息,可以得知多出来的100多个字节,估计是就是这一串字符串吧。

              JConsoleApplication3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null   ConsoleApplication3.Student       

 


延伸阅读:
http://blogs.msdn.com/b/cbrumme/archive/2003/04/15/51326.aspx
 
文章转自:http://www.cnblogs.com/supperwu/archive/2013/05/16/3082061.html
<think>嗯,用户问的是C#中实例化对象后内存占用的决定因素。这个问题非常基础但极其重要,尤其对性能敏感的开发场景。用户已经在技术领域钻研得挺深,从连续提问来看应该是有实际开发需求,而不仅仅是理论好奇。先梳理几个核心点:对象头和字段对齐是常被忽略的关键。普通开发者可能只关注字段本身大小,但实际32位系统的对象头就占8字节——这足够塞下两个整数字段了!上次回复时特意用Person类示例说明:5字节字段对齐后变8字节,加上头总共24字节,用户反馈不错说明这个具象化表达有效。注意到用户引用了静态成员的内容,这其实是个常见误解点。从对话历史看用户可能混淆了静态字段和实例字段的内存分配,需要特别强调:静态字段属于类型而非实例。引用[2]那段说得很好,但用户提问专注在实例化,可以弱化静态部分。内存对齐规则是另一个魔鬼细节。比如bool+int组合在x64系统会占16字节(1+3填充+4实际+8对齐),而调整顺序就能省下填充空间。虽然引用里没提,但实际开发中这类优化很实用,值得补充。用户没问但潜在相关的是值类型优化。struct在栈分配能避免对象头开销,像Point(x,y)用结构体比class省20多字节。不过值类型有拷贝语义的问题,需要权衡——这个点太深入了下次再展开。最后计算工具那块实用性强。sizeof在unsafe环境受限,用GC.GetTotalMemory虽然原始但有效。更专业的方案是加载SOS调试库,不过普通开发者用前两种就够了。用户如果继续问再详细介绍高阶方案。</think> 在C#中,对象实例化后的内存占用主要由以下核心因素决定(基于.NET运行时的内存管理机制): --- ### 一、对象的内存构成 #### 1. **对象头(Object Header)** - **类型指针**:指向方法表(Method Table),存储类型元数据(如方法、接口信息) - **同步块索引**:用于线程同步(锁机制) - **大小**: - 32位系统:$8$ 字节(类型指针 $4$ 字节 + 同步块索引 $4$ 字节) - 64位系统:$16$ 字节(类型指针 $8$ 字节 + 同步块索引 $8$ 字节) - *示例:* 空类实例在64位系统中至少占用 $16$ 字节[^1]。 #### 2. **实例字段(Instance Fields)** - **值类型字段**:直接存储二进制值 ```csharp int num; // 占用 $4$ 字节 double value; // 占用 $8$ 字节 ``` - **引用类型字段**:存储指针(不包含引用对象本体) ```csharp string text; // 在64位系统中占用 $8$ 字节(指针大小) ``` - **内存对齐规则**: - CLR按 $4$ 字节(32位)或 $8$ 字节(64位)对齐字段 - *示例:* `bool flag`($1$ 字节)可能被填充至 $4$ 字节(32位系统) #### 3. **填充字节(Padding)** - 为确保字段对齐而自动添加的空白字节 ```csharp class Example { byte a; // 1字节 int b; // 4字节(导致a后有3字节填充) } ``` - 64位系统中总占用:对象头 $16$ 字节 + $(1+3_{padding})$ + $4$ = $24$ 字节 --- ### 二、关键决定因素 #### 1. **字段类型与数量** - 字段越多/字段类型越大 → 内存占用越大 - *优化建议:* 用较小类型(如`short`替代`int`)或合并布尔标志到位域 #### 2. **继承层级** - 派生类会包含所有基类字段 ```csharp class Base { int id; } // 占用:头 + 4字节 class Derived : Base { bool valid; } // 占用:头 + 4 + (1+7_{padding})=32字节(64位) ``` #### 3. **引用对象实际数据** - 引用类型字段仅存储指针,但指向的对象需额外计算内存 ```csharp class Data { byte[] buffer = new byte[1000]; // 对象头16B + 数组长度4B + 1000元素 = 约1020B } ``` #### 4. **内存对齐规则** - 结构体需特殊处理以避免填充浪费 ```csharp [StructLayout(LayoutKind.Sequential, Pack=1)] // 禁用填充 struct Compact { byte a; int b; // 总大小=5字节(无填充) } ``` --- ### 三、静态成员的影响 ```csharp class Test { static int SharedID; // 不属于实例内存!存于全局区[^2] string InstanceName; // 属于实例内存 } ``` - **静态字段**:在类加载时分配到全局内存,不计入对象实例大小 - **实例字段**:随对象实例化分配在堆上 --- ### 四、内存占用计算公式 以64位系统为例: $$ \text{对象大小} = \underbrace{16}_{\text{对象头}} + \sum{\text{字段大小}} + \sum{\text{对齐填充}} $$ --- ### 五、测量方法 ```csharp // 使用GC获取近似值 long startMem = GC.GetTotalMemory(true); var obj = new MyClass(); long endMem = GC.GetTotalMemory(true); Console.WriteLine($"占用: {endMem - startMem} 字节"); ``` > ⚠️ 注意:实际内存还受**垃圾回收器标记开销**、**大对象堆**(>85KB)特殊管理等因素影响[^1]。 --- ### 优化建议 1. **结构体替代类**:小型数据用`struct`避免对象头开销 2. **字段顺序调整**:按大小降序排列减少填充(如`double>int>short>bool`) 3. **对象池复用**:减少频繁实例化 4. **避免装箱**:`ArrayList`改用`List<T>`
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值