81.CSharp计算对象占用内存
81.1 题目
C#中怎么计算对象占用多少内存?
81.2 深入解析
值类型对象
由其所有字段的内存空间总和,以及可能的对齐填充决定的
CLR 会对值类型进行内存对齐。内存对齐会插入填充字节,使每个字段的起始位置对齐到特定的字节边界(通常是 4 或 8 字节)
引用类型对象
引用类型对象的内存空间由其实例字段的大小、对象头的大小,以及可能的对齐填充决定
其中对象头是每个引用类型对象都有的一个对象头,包含同步块索引和类型对象指针
在32位平台上,通常占8个字节;在64位平台上,通常占16个字节
注意的是
- 在 32 位平台上,指针大小为 4 字节;在 64 位平台上,指针大小为 8 字节。
这影响到对象头的大小和引用类型字段的大小 - 编译器可能会进行优化,以减少内存对齐所需的填充字节。不同的CLR实现可能会有所不同
示例代码
以下是计算简单对象内存大小的示例代码:
using System;
using System.Runtime.InteropServices;
class Program
{
// 定义一个简单的类
class MyClass
{
public int a;
public double b;
}
static void Main()
{
// 计算值类型的大小
Console.WriteLine($"int 类型的大小: {Marshal.SizeOf(typeof(int))} 字节");
Console.WriteLine($"double 类型的大小: {Marshal.SizeOf(typeof(double))} 字节");
// 计算引用类型的大小
MyClass myClass = new MyClass();
int objectHeaderSize = IntPtr.Size * 2; // 对象头的大小(同步块索引和类型对象指针)
int instanceSize = Marshal.SizeOf(myClass.a) + Marshal.SizeOf(myClass.b); // 实例字段的大小
int totalSize = objectHeaderSize + instanceSize; // 总大小
Console.WriteLine($"MyClass 对象的大小: {totalSize} 字节");
// 注意内存对齐和填充字节
// 在32位平台上,IntPtr.Size 为 4;在64位平台上,IntPtr.Size 为 8
}
}
81.3 答题示例
“在C#中计算对象占用的内存,需要区分值类型和引用类型,方法和组成略有不同:
对于值类型(如struct),内存大小由所有字段的总大小加上内存对齐产生的填充字节决定。例如一个包含
int(4B)和byte(1B)的struct,理论上5B,但因对齐(int需4B对齐),实际占8B(byte后填充3B)。可通过Marshal.SizeOf(typeof(MyStruct))获取非托管布局下的大小。对于引用类型(如class),内存由三部分组成:
- 对象头(固定大小):32位平台占8B(4B同步块索引+4B类型指针),64位平台占16B(8B+8B);
- 实例字段总大小:所有值类型字段的大小+引用类型字段的指针大小(32位4B/64位8B);
- 对齐填充:确保总大小是平台字长的倍数(32位4B/64位8B)。
计算时需累加这三部分,例如64位下一个仅含int字段的class,大小为16B(头)+4B(int)+4B(填充)=24B。注意:
Marshal.SizeOf不适用于引用类型的托管内存计算(仅反映非托管布局),托管环境下可借助GC.GetTotalMemory间接估算,或用ClrMD等工具分析实际内存布局。”
81.4 关键词联想
- 值类型 vs 引用类型
- 对象头(同步块索引 + 类型对象指针)
- 内存对齐(Padding)
Marshal.SizeOfGC.GetTotalMemory- 32位/64位差异(字长影响)
- 实例字段(值类型大小 + 引用指针大小)
ClrMD(调试工具)- 非托管布局 vs 托管布局
- 同步块索引(Sync Block Index)
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com