我试图找出我的对象占用了多少内存,以查看其中有多少最终在大对象堆上(超过 85,000 字节)。
是否像为 int 添加 4、为 long 添加 8、为每个对象的任何引用类型等添加 4(或 8,如果您使用 64 位,则为 8)一样简单,或者是否有方法、属性等的开销?
我试图找出我的对象占用了多少内存,以查看其中有多少最终在大对象堆上(超过 85,000 字节)。
是否像为 int 添加 4、为 long 添加 8、为每个对象的任何引用类型等添加 4(或 8,如果您使用 64 位,则为 8)一样简单,或者是否有方法、属性等的开销?
不要忘记实际对象的大小不包括它引用的任何对象的大小。
唯一可能最终出现在大型对象堆上的是数组和字符串——其他对象本身往往相对较小。即使是具有(比如说)10 个引用类型变量(x86 上每个 4 个字节)和 10 个 GUID(每个 16 个字节)的对象也只会占用大约 208 个字节(类型引用和同步块有一点开销)。
同样,在考虑数组的大小时,不要忘记如果元素类型是引用类型,那么只有引用的大小才对数组本身计数。换句话说,即使你有一个包含 20,000 个元素的数组,即使它引用了更多的数据,数组对象本身的大小也只会超过 80K(在 x86 上)。
请按照以下步骤获取对象的大小。
转到 Visual Studio 2010 项目属性 →调试选项卡 →启用非托管代码调试。
转到 Visual Studio调试菜单 →选项和设置→调试→符号。
在那里,启用 Microsoft 符号服务器并保留默认值(符号可能会开始下载)。
在代码中设置断点,开始调试 ( F5)。
打开调试→窗口→立即窗口。
进入.load sos.dll
(罢工之子)
输入!DumpHeap -type MyClass
(要查找大小的对象)
从输出中,定位到对象的地址,即 (00a8197c)
地址 MT 大小 00a8197c 00955124 36
下一个,!ObjSize 00a8197c
你去 → sizeof(00a8197c) = 12 (0x48) 字节 (MyClass)
如果可以的话 - 序列化它!
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
您正在进入高级 .NET 调试领域。从John Robins 调试书籍开始。
将WinDBG与 Sos.dll(.NET 发行版的一部分)和Sosex.dll扩展一起使用。使用这些工具,您可以真正看到应用程序运行时发生的情况。您将找到上述问题的答案。
(另一个建议是安装Shared Source CLI 2.0,又名 Rotor 2,以查看引擎盖下发生了什么。)
Gomes 的方法简化:
转到 Visual Studio (2010) 项目属性* →调试选项卡 →启用非托管代码调试。
在代码中设置断点,开始调试 ( F5)。
打开调试→窗口→立即窗口。
进入.load sos
输入(将 myObject 替换为您的对象的名称)
? String.Format("{0:x}",Integer.Parse(System.Runtime.InteropServices.GCHandle.InternalAddrOfPinnedObject(System.Runtime.InteropServices.GCHandle.Alloc( myObject ).GetHandleValue()).ToString())
使用结果作为参数!ObjSize
请参阅:SOS.DLL、对象地址和 Visual Studio 调试器简介
示例(我们正在寻找名为 的对象tbl
):
.load sos
extension C:\Windows\Microsoft.NET\Framework\v4.0.30319\sos.dll loaded
? string.Format("{0:x}",Integer.Parse(System.Runtime.InteropServices.GCHandle.InternalAddrOfPinnedObject(System.Runtime.InteropServices.GCHandle.Alloc(tbl).GetHandleValue()).ToString())-4)
"27ccb18"
!ObjSize 27ccb18
PDB symbol for clr.dll not loaded
sizeof(027ccb18) = 154504 ( 0x25b88) bytes (System.Data.DataTable)
除非它是一个巨大的值类型或实例类型(即数千个字段),否则您需要担心的唯一类型是大型数组或字符串。当然,要计算出数组的大小,你需要知道元素的大小。
.NET(当前)对齐类型的方式与本机编译器对齐类型的方式非常相似。基本类型具有自然对齐,通常是最接近其大小的两个整数幂:
Single, Int32, UInt32 - 4
IntPtr, UIntPtr, pointers, references - 4 on 32-bit, 8 on 64-bit
Double, Int64, UInt64 - 8
Char, Int16, UInt16 - 2
Byte, SByte - 1
组装类型时,编译器将确保任何给定类型的所有字段在实例内的起始偏移量与匹配该类型的边界对齐 - 假设未使用显式布局。
用户定义类型本身具有对齐方式,该对齐方式被计算为其任何字段类型的最高对齐方式。如果需要,可以扩展类型的大小以使类型的大小也对齐。
但是当然,所有引用类型在大小和对齐方式上仍然只有 IntPtr.Size,所以引用类型的大小不会影响该类型的数组。
请注意,CLR 可以自行选择与上述不同的布局类型,可能是为了增加缓存局部性或减少对齐所需的填充。
作为估计(2017 年),您可以调试应用程序,在字典生效之前设置断点,拍摄“内存使用快照”(标签:诊断工具下的内存使用),填写字典并获取另一个快照。
这并不准确,但它是一个很好的猜测。
在这种情况下Dictionary<TKey, TValue>
,您可以通过二进制序列化来获取对象大小。这是一个示例代码:
var dictionary = new DictionaryGenerator().GetSomeLargeDictionary();
var memoryStream = new System.IO.MemoryStream();
var binaryFormatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
binaryFormatter.Serialize(memoryStream, dictionary);
Console.WriteLine($"Size of dictionary: {memoryStream.Position} byte(s)");