最近在系統的讀CLR via C#這本書,發現寫得很好。但是抽象的概念比較多,有些地方理解起來表費勁耗時,所以在這里記錄下自己覺得重要的地方。
本文要闡述的主要內容:在運行時,值類型和引用型是如何在線程棧和托管堆中工作的。
線程棧的基本概念及資源分配:
線程棧的創建:在windows進程加載完CLR,創建一個線程后,大小為1MB的線程棧被創建。
線程棧的作用:存儲形參和局部變量。如圖所示name和m2方法的形參將會被存放在線程棧中。
疑問:為什么全局變量不存放在線程棧中?全局變量應該是在類里面,類作為引用類型自然是存放在托管堆中。
圖4-3 執行String name="Joe"后,CLR會在線程棧中name分配空間。
圖4-4
1.執行M2(name)之前,CLR將name的拷貝s作為實參,存放在線程棧中。
2.執行M2(name)之前,CLR將函數的執行返回地址(return address)存放在線程棧中,以便M2方法執行完成好,回到M1中繼續執行。
圖4-5
1.M2方法內的局部變量存放在線程棧中。
2.M2方法執行完成后,清空M2中局部變量,同時取出return address,回到M1方法中繼續執行。此時線程棧中的資源只剩下name了,如圖4-3所示。
托管堆中的資源分配
我們有兩個類定義如下:
internal class Employee {
public Int32 GetYearsEmployed() { ... }
public virtual String GetProgressReport() { ... }
public static Employee Lookup(String name) { ... }
}
internal sealed class Manager : Employee {
public override String GetProgressReport() { ... }
}
圖4-6 windows進程啟動,CLR加載完成,托管堆完成初始化,線程被創建,且執行了一部分代碼,接下來准備執行M3方法。
圖4-8
1.在實行M3方法之前,JIT編譯器將IL代碼編譯成本地CPU指令。
此外JIT編譯器還做了的兩件事情:
(1)通過查詢元數據信息,加載M3方法中引用的所有類型的程序集(如果程序集已經加載,就跳過)。
(2)通過查詢元數據信息,在托管堆中創建引用的類。
2.執行M3中申明變量e和year代碼時,在線程棧中分配兩個變量。
圖4-9
1.執行e=new Manager()時,根據元數據找到對應的模板(Manager Type Object),然后創建實例對象Manager object,並且修改線程棧中變量e的值,讓它指向Manager object對象。
2.對象指針指向相應的類型對應指針。
3.由圖中可以看出對象在托管堆中包括三部分:對象指針,同步索引塊,對象字段。
疑問1:對象的方法存放在哪里?由圖可以看出方法存放在類中。
疑問2: 實例的方法存放在類對象中,多線程訪問實例的方法的過程是什么樣子的?
答:假設方法已經編譯成IL指令集合(包括指令1,指令2,指令3)。線程1進入方法訪問完指令1,2后,保存線程現場。線程2開始訪問指令1,2,此時CPU的權限又移交給線程1,線程1恢復線程現場,接着執行指令3完成后,線程1回到線程池或者關閉。CPU把控制權交給線程2,繼續執行指令3,完成后線程3回到線程池。
疑問2的解答是我個人理解,如果不正確,歡迎指教。
圖4-10
同上一步類似。JIT編輯器講LookUp方法編譯成IL指令,並執行指令,在托管堆中創建Joe對象。
圖4-11 JIT編譯器將GetYearsEmployed編譯成CPU指令,並執行后,將值存放在線程棧的Year變量中。
圖4-12
(1)執行e.GenProgressReport方法時,難點在於是執行基類還是子類中的方法。
(2)對於虛方法的執行,需要檢查變量類型(Employee)和托管堆類型(Manager)是否一致,如果不一致,需要做額外的處理。
(3)不一致的話,需要通過對象指針定位到類型對象,然后找到對應的方法。因為Manage中改方法前面是Override,所以找到的是Manager類中的GenProgressReport方法。
疑問:如果Manger中的GenProgressReport方法前面是用new修飾的,托管堆的圖該怎么畫?e.GenProgressReport方法應該如何執行?
答:1)類對象(Manager Type Object)應該生成兩個同名方法,並且在元數據中做好標記(該方法是否為子類中的方法)。
2)執行e.GenProgressReport方法時,因為類對象中有兩個方法,根據引用變量e的類型為基類,應該調用基類中繼承過來的同名方法。
疑問2:類A繼承類B,里面的方法,相同的方法在內存中是一塊還是兩塊?
答:這個問題不是本文重點,將在下一篇博客中解決。
圖4-13 Employee和Manager的類型對象是System.Type類型的實例。所以在CLR加載完成,托管堆初始化后,立馬在托管對中創建System.Type類空間。