使用WinDBG + SOS谈对象大小及字符串的结构
2009-12-01 14:57 Jeffrey Zhao 阅读(6360) 评论(21) 收藏 举报昨天我们使用了一个最最简单的小实验,来检查相同类型的不同对象大小是否相同。当然,我们很轻易地“验证”得出,不同长度的字符串大小是不一样的。不过这种表面现象其实很难说明问题,因此我现在还是用WinDBG + SOS来进行一些检查,希望可以得到一些表面上看不出来的信息。
准备工作
首先,您需要先去下载并安装WinDBG(不过,其实它是纯绿色的)。我的机器是32位系统,如果您使用64位的机器进行实验,那么一些结果便会有所不同。不过,大致方式还是差不多的。装好了WinDBG之后,您可以获取任意一个.NET应用程序的Dump文件。例如,您可以随意写一个Console应用程序,就在Main方法里放一个Console.ReadLine调用,然后便可以在程序停止时使用如下命令获取一个Dump文件(其中xxxx为进程的pid):
adplus -p xxxx -hang -quiet
然后您可以打开WinDBG的GUI窗口,配置一个Symbol File Path,例如:
SRV*d:\symbol-cache*http://msdl.microsoft.com/download/symbols
再打开刚才的Dump文件(Open Crash Dump),加载SOS,如:
.load C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\sos.dll
接着便可以开始了。
检查普通对象大小
首先,随意检查一下堆上有哪些对象:
0:000> !dumpheap -stat
total 657 objects
Statistics:
MT Count TotalSize Class Name
65d7fc7c 1 12 System.__Filters
65d7fc2c 1 12 System.Reflection.Missing
65d73cc4 1 12 System.Security.Permissions.SecurityPermissionFlag
65d6c3a4 1 12 System.Security.Permissions.FileDialogPermission
65d6c368 1 12 System.Security.PolicyManager
65d55604 1 12 System.Security.Permissions.ReflectionPermission
...
65d8224c 3 192 System.IO.UnmanagedMemoryStream
65d80770 18 216 System.Object
65d67864 8 256 System.Collections.ArrayList+SyncArrayList
65d83118 8 288 System.Security.Util.TokenBasedSet
65d81cd4 19 380 System.RuntimeType
65d82818 12 432 System.Security.PermissionSet
65d831a8 9 504 System.Collections.Hashtable
65d82e7c 20 560 System.Collections.ArrayList+ArrayListEnumeratorSimple
65d82b84 30 720 System.Collections.ArrayList
65d832a4 9 1296 System.Collections.Hashtable+bucket[]
65d835c4 10 1852 System.Byte[]
65d82cf0 15 2192 System.Int32[]
65d81784 28 3540 System.Char[]
65d54324 51 18568 System.Object[]
65d80b54 258 21612 System.String
于是我们发现,堆上有许多类型的对象,每个类型的对象数量不一,那么我们检查一下某个特定类型的对象呢?例如PermissionSet对象:
0:000> !dumpheap -mt 65d82818
Address MT Size
01df1ab8 65d82818 36
01df1adc 65d82818 36
01df7288 65d82818 36
01df7370 65d82818 36
01df74e4 65d82818 36
01df7774 65d82818 36
01df7808 65d82818 36
01df7e84 65d82818 36
01dfa4d8 65d82818 36
01dfa514 65d82818 36
01dfa688 65d82818 36
01dfa838 65d82818 36
total 12 objects
Statistics:
MT Count TotalSize Class Name
65d82818 12 432 System.Security.PermissionSet
Total 12 objects
然后看Hashtable:
0:000> !dumpheap -mt 65d831a8
Address MT Size
01df4974 65d831a8 56
01df78c4 65d831a8 56
01df798c 65d831a8 56
01df8924 65d831a8 56
01df8f8c 65d831a8 56
01df9054 65d831a8 56
01df911c 65d831a8 56
01df92ec 65d831a8 56
01dfbd80 65d831a8 56
total 9 objects
Statistics:
MT Count TotalSize Class Name
65d831a8 9 504 System.Collections.Hashtable
Total 9 objects
最后再看看ArrayList:
0:000> !dumpheap -mt 65d82b84
Address MT Size
01df1cec 65d82b84 24
01df2154 65d82b84 24
01df21b8 65d82b84 24
01df2310 65d82b84 24
01df474c 65d82b84 24
01dfa6ac 65d82b84 24
01dfa7f4 65d82b84 24
...
01dfa85c 65d82b84 24
01dfad48 65d82b84 24
01dfb220 65d82b84 24
01dfb414 65d82b84 24
01dfb574 65d82b84 24
01dfb6d8 65d82b84 24
01dfbb28 65d82b84 24
total 30 objects
Statistics:
MT Count TotalSize Class Name
65d82b84 30 720 System.Collections.ArrayList
Total 30 objects
这样看来,普通类型的每个对象大小都是相同的,不是吗?
字符串的大小及结构
不过,字符串又如何呢?
0:000> !dumpheap -mt 65d80b54 -min 100
Address MT Size
01df11c8 65d80b54 244
01df12bc 65d80b54 292
01df190c 65d80b54 112
01df197c 65d80b54 112
01df19ec 65d80b54 204
01df1b84 65d80b54 296
01df252c 65d80b54 2216
01df2dd4 65d80b54 3384
01df42f4 65d80b54 124
01df4370 65d80b54 188
01df442c 65d80b54 164
01df4524 65d80b54 156
01df45f4 65d80b54 216
01df7a54 65d80b54 120
01df7b1c 65d80b54 296
01df81e4 65d80b54 112
01df85a0 65d80b54 168
01df8664 65d80b54 264
01df9554 65d80b54 148
01df97d4 65d80b54 148
01df9868 65d80b54 276
01df997c 65d80b54 532
01df9b90 65d80b54 1044
01dfa918 65d80b54 280
01dfaba0 65d80b54 280
01dfb284 65d80b54 280
01dfb438 65d80b54 244
01dfb5cc 65d80b54 244
01dfbb4c 65d80b54 244
01dfc050 65d80b54 164
total 30 objects
Statistics:
MT Count TotalSize Class Name
65d80b54 30 12552 System.String
Total 30 objects
由于字符串数量有些多(258个),因此我们只查看那些体积大于100字节的对象。从结果中可以发现,String对象与其它类型不同,它的对象大小不一。那么我们随意查看其中最小的一个会是什么样的:
0:000> !do 01df190c
Name: System.String
MethodTable: 65d80b54
EEClass: 65b3d65c
Size: 110(0x6e) bytes
(C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: C:\Windows\Microsoft.NET\Framework\v2.0.50727\
Fields:
MT Field Offset Type VT Attr Value Name
65d82da0 4000096 4 System.Int32 1 instance 47 m_arrayLength
65d82da0 4000097 8 System.Int32 1 instance 46 m_stringLength
65d81834 4000098 c System.Char 1 instance 43 m_firstChar
65d80b54 4000099 10 System.String 0 shared static Empty
>> Domain:Value 00336628:01df1198 <<
65d81784 400009a 14 System.Char[] 0 shared static WhitespaceChars
>> Domain:Value 00336628:01df1898 <<
可以发现,一个String对象包含3个明显的字段,它们的偏移量分别是4、8、12——那么偏移量为0的是什么,一个字符串其他100个字节又存放了什么呢?那么继续,打印出地址上的内容吧:
0:000> dd 01df190c 01df190c 65d80b54 0000002f 0000002e 003a0043 01df191c 0057005c 006e0069 006f0064 00730077 01df192c 004d005c 00630069 006f0072 006f0073 01df193c 00740066 004e002e 00540045 0046005c 01df194c 00610072 0065006d 006f0077 006b0072 01df195c 0076005c 002e0032 002e0030 00300035 01df196c 00320037 005c0037 00000000 00000000 01df197c 65d80b54 0000002f 0000002e 003a0043
上面的数据中,从左往右第一列是地址,而第二列开始则是地址上的数据。从中我们可以看出:
- 偏移量0:String的MethodTable(65d80b54)。
- 偏移量4:m_arrayLength的值47(0000002f)。
- 偏移量8:m_stringLength的值46(0000002e)。
而接下来则是字符串的内容了,此时可以每2字节一看,并注意Byte Order:
- 偏移量12:字符“C”(0043)
- 偏移量14:字符“:”(003a)
- 偏移量16:字符“\”(005c)
- 偏移量18:字符“W”(0057)
- ……
也就是说,String内部似乎就包含了一大堆内存空间,其中包含了字符串的内容。那么,字符串的长度和大小又有什么关系呢?
字符串的“长度”与“容量”
从刚才的分析中,或是.NET Reflector的反编译结果,我们可以知道一个String对象内部包含了三个字段,其中m_stringLength是字符串的长度,那么m_arrayLength又是什么呢?我又随意挑了几个字符串进行查看,不知不觉打开了其中一个1000多字节的字符串:
0:000> !do 01df9b90
Name: System.String
MethodTable: 65d80b54
EEClass: 65b3d65c
Size: 1042(0x412) bytes
(C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: <PermissionSet class="System.Security.PermissionSet"
version="1">
<IPermission class="System.Security.Permissions.SecurityPermission, ..."
version="1"
Flags="SkipVerification"/>
</PermissionSet>
Fields:
MT Field Offset Type VT Attr Value Name
65d82da0 4000096 4 System.Int32 1 instance 513 m_arrayLength
65d82da0 4000097 8 System.Int32 1 instance 273 m_stringLength
65d81834 4000098 c System.Char 1 instance 3c m_firstChar
65d80b54 4000099 10 System.String 0 shared static Empty
>> Domain:Value 00336628:01df1198 <<
65d81784 400009a 14 System.Char[] 0 shared static WhitespaceChars
>> Domain:Value 00336628:01df1898 <<
这个字符串有些特别,它的长度是273,但是m_arrayLength确是513,因此占用了1042个字节。查看多个字符串我们便可发现,一个字符串所占用的体积其实是16 + m_arrayLength * 2。这显得有些奇怪,不禁让我想到了StringBuilder中“容量”的概念。StringBuilder会维护一个当前最大容量,这样便可以向SB内部不断地添加字符串。但是String对象难道不是不可变的吗?那为什么要保持m_arrayLength这样一个“容量”的概念呢?
这次我们就先探索到这里,我们下次再来看这个问题。
顺风车
有前端开发的兄弟吗?老赵需要你……
浙公网安备 33010602011771号