在這篇博文中,可能要說的東西比較難,筆者本來准備前天就進行更新的,但是反復的斟酌一直找不到合適的語句去表述,也可能筆者自己理解的也不是很透徹,所以,在這篇博文中,有不對的地方,望廣大網友指出,並給予糾正,大家共同進步嘛,下面開始。
在我們寫完一個程序模塊的時候,在提交給PM進行檢驗的時候,自己都會先運行一下找一些BUG進行測試,並改正。但是,在我們運行調試模塊的時候,CLR到底是怎麼樣進行加載程序的呢?
當CLR開始加載一個進程之後,在進程中會有很多線程,當一個線程創建的時候,CLR會自動分配1MB的堆棧空間,並把當前方法所需要的參數、方法內部定義的局部變量進行儲存,並負責傳遞下一個方法的參數。當JIT編譯該方法為本地CPU指令前,CLR要確保該方法中定義的所有程序集都加載到當前的APPDomain中,並將該方法中的所有局部變量進行一個初始化的操作(將變量初始化為null或者0)然後,CLR利用程序集的元數據來提取類型的信息。說到這裡,就又引出一些知識,CLR是如何知道這些類型對象裡面有什麼方法和屬性的呢?在我們進行編譯的時候,沒一個類型都對應了三張表,在下只介紹元數據定義表,因為篇幅太長
元數據定義表:
在編譯器編譯源代碼時,代碼中的任何一樣都會在這個表中插入一條相應的記錄。該表中包含:
1、 ModuleDef:包含模塊的標示記錄,包括文件名和擴展名,並由GUID生成的一個ID;
2、 TypeDef:包含模塊中任意一個定義的類型名稱、基類、標示(public\private等),以及指向MethodDef、FieldDef、ParamDef、PropertyDef以及EventDef的索引;
3、 MethodDef:包含模塊中定義的方法的名稱、標示(private public等)以及指向ParamDef的索引;
4、 FieldDef:包含模塊中定義的每個字段的名稱、標示(public\private等)、類型;
5、 ParamDef:包含模塊中定義的每個參數的名稱、標示(in\out\ref)、類型;
6、 PropertyDef:包含模塊中定義的每個屬性的名稱、標示、類型;
7、 EventDef:包含模塊中定義的每個時間的名稱和標識
這就是元數據定義表的大概的一個組成部分。還有其他兩張表,在這裡就不一一介紹了。
筆者認為CLR就是通過這張表中,找到對象引用需要的所有程序集的元數據的並進行加載、壓入到堆棧空間中並執行的。那麼具體執行的是怎麼一回事呢?由於筆者怕自己的理解有誤,畫出的圖更不敢粘貼在博文中,怕誤導,所以拿出原書中的圖,筆者將在圖下寫出筆者的見解,望各位見諒!首先我們假設有兩個類,如下:
www.2cto.com
internal class Employee
{
public Int32 GetYearsEmploye()
{
return -1;
}
public virtual String GenProcessReport()
{
return string.Empty;
}
public static Employee LockUp(String name)
{
return null;
}
}
internal class Manager : Employee
{
public override string GenProcessReport()
{
return base.GenProcessReport();
}
}
假設當Windows進程啟動之後,CLR也已經加載需要的程序集元數據並且也對堆棧進行了初始化並對當前線程分配了1MB的堆棧空間。如圖:
大家看到的線程堆棧中有陰影的部分,我們可以理解為該線程已經執行了部分代碼,馬上要開始執行M3方法。根據CLR的運行機制,首先需要JIT將M3方法編譯成本地的CPU指令,與此同時,CLR要確保APPDomain中已經加載了M3方法中的所有類型。然後通過元數據定義表找到M3需要的所有相關信息,並在堆上面構建一個數據表來表示Employee、Manager類型的對象,如圖:
這個時候,CLR確定方法所需要的所有類型都已經創建好之後,並M3方法的代碼編譯完成之後,准許執行代碼。在執行代碼的過程中,CLR都會自動初始化對象的成員,並初始化對象實例的字段為null或者為0,這些都做完之後,才會執行我們在代碼中所寫的構造函數(在上一篇文章中,我給大家介紹了如何創建對象實例)如圖:
上圖表示的是已經調用了Employee的構造器,並在堆中創建實例,類型對象指針返回對象內存地址。
那麼接下來,我們代碼需要執行一個靜態方法為“Lookup”,當這個方法執行時,CLR會定位到靜態方法所屬的類型,然後通過JIT進行編譯(如果JIT已經編譯好則無需再次編譯)。Lockup方法會在內部再次創建一個新的Manager對象,返回該對象的地址,並由e局部變量進行保存。如圖:
在這個,e保存的對象地址不再是之前的Manager對象的地址,這個時候的地址為Employee的對象地址,而之前的Manager對象不再有對象進行引用,等待GC進行回收,而且它成為GC回收的主要對象。在執行到year=e.GetYearsEmployed();方法的時候,CLR同樣要找到該方法的記錄項(筆者認為在元數據定義表中)然後JIT進行編譯(如果已經編譯過JIT無需再次編譯)並返回數據給year變量,如圖:
最後一步,當我們運行到e.GetProgressReport()這個方法的時候,CLR將根據方法查找相應的對象,這個時候e變量又指向Manager對象,因為在Employee對象中的GetProgressReport()虛方法的最終實現在Manager類中。CLR同樣要找到該方法的記錄項(筆者認為在元數據定義表中)然後JIT進行編譯(如果已經編譯過JIT無需再次編譯)。
至此,關於程序運行時的關系,是筆者的見解,如有錯誤的地方,請各位園友之處,並給予糾正。謝謝!
作者 LouisLee