Når vi debugger .NET applikationer med WinDbg, vil vi almindeligvis have brug for at inspicere heapen med !dumpheap-kommandoen. Den viser antallet af instanser af de forskellige typer på heapen. Listen er sorteret efter summen af størrelsen for instanserne af de enkelte typer på heapen. Dog skal det bemærkes, at der er tale om selve typernes størrelse, så størrelsen af en type med referencer inkluderer altså ikke størrelsen på de refererede objekter.
Det vil typisk sige, at typer med mange instanser vil være at finde mod slutningen af listen, og blandt dem finder vi meget ofte string og object[], men hvorfor er der så mange af disse i stort set alle .NET applikationer?
String
Lad os begynde med string, for her er forklaringen oplangt. For det første opretter CLRen en hel del strings til diverse konstanter, stier og så videre og jo flere assemblies vi refererer, desto flere af disse får vi med i bagagen. Dertil kommer at instanser af string som bekendt er immutable, så hvis applikationen laver en eller anden form for string-behandling, skaber det hurtig mange instanser. Husk at alle konstante tekster samt alle konstruerede tekster havner på heapen, så derfor løber det hurtig op. Med mindre der er tale om meget store instanser, er det sjældent værd at bekymre sig om.
Object[]
Antallet af string-instanser kan virke overvældende, men når vi tænker over det, er det i virkeligheden ikke så underligt, men hvad med object[]?
Var vi ikke lige blevet enige om, at generics og typestærke collections er sådan en god ide? Hvorfor skulle vi så bruge object[]? Og hvis det ikke er vores applikation, der bruger alle disse forældede collections, er det så Microsofts klasser, der endnu ikke er blevet opdateret?
Forklaringen er faktisk mere subtil end som så. Den underliggende type af List<T> er faktisk object[]. Hvordan kan det passe? Når vi fyrer op under Reflector, kan vi jo tydelig se, at List<T> benytter T[] til opbevaring af elementerne. Så hvis vi har List<string>, får vi vel string[] som den underliggende type. Ja, og nej.
Den underliggende type af List<string> er ganske rigtig string[], men måden CLRen implementerer arrays for referencetyper er ved hjælp af object[]. Lad os se et eksempel. Hvis vi har følgende i Main(), kan vi let finde den aktuelle List<string>-instans ved hjælp af !dso.
var Simpsons = new List<string> {
"Homer", "Marge", "Bart", "Lisa", "Maggie"
};
0:000> !dso OS Thread Id: 0x1390 (0) ESP/REG Object Name 0029ed28 01ea2b78 Microsoft.Win32.SafeHandles.SafeFileHandle 0029ed38 01ea2b78 Microsoft.Win32.SafeHandles.SafeFileHandle 0029ed6c 01ea42ac System.Byte[] 0029ed70 01ea2b8c System.IO.__ConsoleStream 0029ed90 01ea4034 System.IO.StreamReader 0029ed94 01ea4034 System.IO.StreamReader 0029edac 01ea4034 System.IO.StreamReader 0029edb0 01ea45c4 System.IO.TextReader+SyncTextReader 0029edd0 01ea45c4 System.IO.TextReader+SyncTextReader 0029ede0 01ea2af4 System.Collections.Generic.List`1[[System.String, mscorlib]] <=== HER! 0029eea4 01ea2a38 System.Object[] (System.String[]) 0029f050 01ea2a38 System.Object[] (System.String[]) 0029f078 01ea2a38 System.Object[] (System.String[])
Vores instans findes på adressen 01ea2af4, så lad os se nærmere på den.
0:000> !do 01ea2af4
Name: System.Collections.Generic.List`1[[System.String, mscorlib]]
MethodTable: 5f5f58c8
EEClass: 5f3abf0c
Size: 24(0x18) bytes
(C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
Fields:
MT Field Offset Type VT Attr Value Name
5f5c4eec 40009d8 4 System.Object[] 0 instance 01ea2b3c _items
5f5eab0c 40009d9 c System.Int32 1 instance 5 _size
5f5eab0c 40009da 10 System.Int32 1 instance 5 _version
5f5e84dc 40009db 8 System.Object 0 instance 00000000 _syncRoot
5f5c4eec 40009dc 0 System.Object[] 0 shared static _emptyArray
WinDbg reporterer korrekt, at vi har fat i List<string>, men læg også mærke til, at vi blandt Fields finder _items af typen Object[]. Er det vores data? Lad os se nærmere på indholdet af _items.
0:000> !dumparray -details -nofields 01ea2b3c
Name: System.String[]
MethodTable: 5f5c4eec
EEClass: 5f3aa8a0
Size: 48(0x30) bytes
Array: Rank 1, Number of elements 8, Type CLASS
Element Methodtable: 5f5e88c0
[0] 01ea2a64
Name: System.String
MethodTable: 5f5e88c0
EEClass: 5f3aa498
Size: 28(0x1c) bytes
(C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: Homer
[1] 01ea2a80
Name: System.String
MethodTable: 5f5e88c0
EEClass: 5f3aa498
Size: 28(0x1c) bytes
(C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: Marge
[2] 01ea2a9c
Name: System.String
MethodTable: 5f5e88c0
EEClass: 5f3aa498
Size: 26(0x1a) bytes
(C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: Bart
[3] 01ea2ab8
Name: System.String
MethodTable: 5f5e88c0
EEClass: 5f3aa498
Size: 26(0x1a) bytes
(C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: Lisa
[4] 01ea2ad4
Name: System.String
MethodTable: 5f5e88c0
EEClass: 5f3aa498
Size: 30(0x1e) bytes
(C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: Maggie
[5] null
[6] null
[7] null
Der er vist ingen tvivl om, at det object[] indeholder navnene på vores gule familie, men hvor er sammenhængen mellem List<string> og det underliggende object[]? Hvordan ved CLRen, at vores array indeholder instanser af string?
Lad os først få en ting på det rene. Referencer er referencer, så der er ingen forskel på en reference til en instans af object og en reference til en instans af string. Det er blot referencer, så der er intet galt i, at CLRen bruger et object[] til at gemme referencerne, men hvordan holder den styr på det faktiske indhold?
Ovenstående dump giver ikke nogen svar på dette spørgsmål. SOS kan give indtrykket af, at vi ser det hele, men faktisk pakker SOS et par implementeringsdetaljer ind og viser os dem på anden vis. Vi er altså nødt til at gå uden om SOS, for at få svar på det spørgsmål.
Dumper vi hukommelsen for vores object[], får vi de manglende brikker.
0:000> dd 01c62b3c -4 01c62b3c -4 +0x30 -1 01c62b38 00000000 5f5c4eec 00000008 5f5e88c0 01c62b48 01c62a64 01c62a80 01c62a9c 01c62ab8 01c62b58 01c62ad4 00000000 00000000 00000000
Den noget kryptiske kommando kalder vist på en forklaring. Den instansadresse SOS reporterer, er faktisk forskudt med et enkelt word. Det vil sige, at data for en instans begynder 4 bytes før den viste adresse (på 32 bit forstås). Så for at udskrive indholdet af instansen er vi nødt til at gå 4 bytes tilbage og udskrive til og med instansadressen, minus de fire bytes, plus størrelsen af instansen minus en. Det giver os data for vores object[].
Det første word er instansens SyncBlock, derefter følger instansen egen Method Table, så kommer længden at arrayet (8 i dette tilfælde), og herefter kommer det interessante: Hvad gemmer der sig bag 5f5e88c0? At dømme på værdien kunne det være en Method Table, så lad os se om det skulle være tilfældet.
0:000> !dumpmt 5f5e88c0 EEClass: 5f3aa498 Module: 5f381000 Name: System.String mdToken: 02000024 (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll) BaseSize: 0x10 ComponentSize: 0x2 Number of IFaces in IFaceMap: 7 Slots in VTable: 196
Det er en Method Table for string og dermed har vi kædet vores object[] sammen med den aktuelle type. De efterfølgende otte words er referencerne til vores fem string-instanser, samt tre tommer pladser til yderligere string-referencer.
Det efterlader blot et spørgsmål: Hvis object[] i realiteten er T[], hvad indeholder det mystiske felt så for et rigtig object array? Method Table for object naturligvis og så passer pengene.