代碼案例
public class UserInfo
{
private Int32 age = -1;
private char level = 'A';
}
public class User
{
private Int32 id;
private UserInfo user;
}
public class VIPUser : User
{
public bool isVip;
public bool IsVipUser()
{
return isVip;
}
public static void Main()
{
VIPUser aUser;
aUser = new VIPUser();
aUser.isVip = true;
Console.WriteLine(aUser.IsVipUser());
}
}
将上述代碼的執行過程,反編譯為 IL 語言可知:new 關鍵字被編譯為 newobj 指令來完成對象建立工作,進而調用類型的構造器來完成其初始化操作,然後我們描述執行的具體過程:
首先,将聲明一個引用類型變量 aUser:
VIPUser aUser;
它僅是一個引用(指針),儲存線上程的堆棧上,占用 4Byte 的記憶體空間,将用于儲存 VIPUser 對象的有效位址,其執行過程正是上文描述的線上程棧上的配置設定過程。此時 aUser 未指向任何有效的執行個體,是以被自行初始化為 null,試圖對 aUser 的任何操作将抛出 NullReferenceException 異常。
接着,通過 new 操作執行對象建立:
aUser = new VIPUser();
如上文所言,該操作對應于執行 newobj 指令,其執行過程又可細分為以下幾步:
1、CLR 按照其繼承層次進行搜尋,計算類型及其所有父類的字段,該搜尋将一直遞歸到System.Object 類型,并傳回位元組總數,以本例而言類型 VIPUser 需要的位元組總數為 15Byte,具體計算為:VIPUser 類型本身字段 isVip(bool 型)為 1Byte;父類 User 類型的字段id(Int32 型)為 4Byte,字段 user 儲存了指向 UserInfo 型的引用,是以占 4Byte,而同時還要為 UserInfo 配置設定 6Byte 位元組的記憶體。
執行個體對象所占的位元組總數還要加上對象附加成員所需的位元組總數,其中附加成員包括 TypeHandle 和 SyncBlockIndex,共計 8 位元組(在 32 位 CPU 平台下)。是以,需要在托管堆上配置設定的位元組總數為 23 位元組,而堆上的記憶體塊總是按照 4Byte 的倍數進行配置設定,是以本例中将配置設定 24位元組的位址空間。
2、CLR 在目前 AppDomain 對應的托管堆上搜尋,找到一個未使用的 20 位元組的連續空間,并為其配置設定該記憶體位址。事實上,GC 使用了非常高效的算法來滿足該請求,NextObjPtr 指針隻需要向前推進 20 個位元組,并清零原 NextObjPtr 指針和目前 NextObjPtr 指針之間的位元組,然後傳回原 NextObjPtr 指針位址即可,該位址正是新建立對象的托管堆位址,也就是 aUser 引用指向的執行個體位址。而此時的 NextObjPtr 仍指向下一個建立對象的位置。注意,棧的配置設定是向低位址擴充,而堆的配置設定是向高位址擴充。
注意:另外,執行個體字段的存儲是有順序的,由上到下依次排列,父類在前子類在後。在上述操作時,如果試圖配置設定所需空間而發現記憶體不足時,GC 将啟動垃圾收集操作來回收垃圾對象所占的記憶體。
最後,調用對象構造器,進行對象初始化操作,完成建立過程。該構造過程,又可細分為以下幾個環節:
(a)構造 VIPUser 類型的 Type 對象,主要包括靜态字段、方法表、實作的接口等,并将其配置設定在上文提到托管堆的 Loader Heap 上。
(b)初始化 aUser 的兩個附加成員:TypeHandle 和 SyncBlockIndex。将 TypeHandle 指針指向 Loader Heap 上的 MethodTable,CLR 将根據 TypeHandle 來定位具體的 Type;将SyncBlockIndex 指針指向 Synchronization Block 的記憶體塊,用于在多線程環境下對執行個體對象的同步操作。
(c)調用 VIPUser 的構造器,進行執行個體字段的初始化。執行個體初始化時,會首先向上遞歸執行父類初始化,直到完成 System.Object 類型的初始化,然後再傳回執行子類的初始化,直到執行 VIPUser 類為止。以本例而言,初始化過程為首先執行 System.Object 類,再執行 User類,最後才是 VIPUser 類。最終,newobj 配置設定的托管堆的記憶體位址,被傳遞給 VIPUser 的 this 參數,并将其引用傳給棧上聲明的 aUser。
上述過程,基本完成了一個引用類型建立、記憶體配置設定和初始化的整個流程,然而該過程隻能看作是一個簡化的描述,實際的執行過程更加複雜,涉及到一系列細化的過程和操作。
對象建立并初始化之後,記憶體的布局,可以表示為:

總結
綜上分析可知,在托管堆中增加新的執行個體對象,隻是将 NextObjPtr 指針增加一定的數值,再次新增的對象将配置設定在目前 NextObjPtr 指向的記憶體空間,是以在托管堆棧中,連續配置設定的對象在記憶體中一定是連續的,這種配置設定機制非常高效。