.NET物件的記憶體佈局

發表於2015-05-04

每個虛擬機器都有它自己的物件佈局,本文我們將針對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。

相關文章