每個虛擬機都有它自己的對象布局,本文我們將針對sscli源碼和windbg調試器來查看不同類型的.net對象布局。
在.net虛擬機里,每個對象都需要保存這些信息:
- 對象的類型;
- 對象實例的成員屬性(field)值;
- hash值、鎖信息等其他數據結構。
普通對象
在CLR里,對象在托管代碼(managed code)和非托管代碼(unmanaged code)里有不同的表現形式。在托管代碼里,所有對象的基類Object類型是在 clr\src\bcl\system\Object.cs里定義,而其在非托管世界里,則復雜的多,由在clr\src\vm\object.h里定義的Object類型,clr\src\vm\SyncBlk.h里定義的ObjHeader等類型實現。對象在非托管代碼理的內存物理布局如下圖所示:
- 在非托管代碼里,對象實際上有兩個指針。
- object指針就是虛擬機返回給托管代碼的對象引用地址,從這個指針開始,托管代碼就可以獲取到任何一個對象的類型以及成員變量信息。
- 而另外一個指針objhead,實際上是非托管代碼里,每個.NET對象實際的指針,在這個指針后面,包含了控制線程同步,甚至是COM Interop相關信息的SyncBlock索引,這個索引的作用我們會在后文提到。因為索引只用到32位字節,所以在64位系統運行的時候,會加上一個填充用的DWORD以便補齊內存邊界。
- object指針后面緊跟的就是該對象所屬的類型:MethodTable,MethodTable顧名思義就是函數表,在.NET里它就是對象類型的代表,在后文我們也會詳細說明它。
- 類型信息后面就是每個對象實例成員變量的值信息了,如果是成員變量是引用類型,那么就保存被引用的對象的object指針(不是objhead),如果是值類型,比如說結構體,那么就按照值類型的內存布局,將變量值直接保存在對象的內存區域里。
雖然.NET里所有引用類型的對象都從Object類型里繼承,一些特殊的對象,在CLR里也在非托管代碼里定義了一份不同的布局,方便虛擬機的處理。
數組對象 - Array
在托管代碼里,其在 clr\src\bcl\system\Array.cs 里定義,在非托管代碼里,其在 clr\src\vm\object.h 里定義。之所以這樣做,是因為數組對象即可以保存引用類型,也可以保存值類型,而且為了方便程序訪問數組的長度,數組對象實際上是將長度信息直接保存在內存里了,如下圖是其內存布局:
- 除了將長度信息直接保存到內存以外,如果是多維數組,則還會將各個緯度的上標和下標信息都保存到內存里,這主要是支持向VB這樣的可以修改數組上下標的編程語言設計的。
- 如果是引用類型,則會把成員元素的類型指針保存在數組里;
- 如果是值類型,則直接保存成員元素的內容。
字符串對象
在托管代碼里,其在 clr\src\bcl\system\String.cs 里定義,在非托管代碼里,其在 clr\src\vm\object.h 里定義。
- 因為在.NET里字符串是不能修改的,可以將其當作數組處理,所以.NET直接將字符串的長度保存在內存里;
- 為了方便非托管代碼處理字符串,每個字符串最后以 NULL 結尾,當然字符類型是WCHAR,而不是CHAR,這也就是說.NET下面字符默認是UNICODE。