.Net运行时的相互关系
閱讀目錄
- 前言
- 線程堆棧的分配
- 托管堆上對象的分配
- 結束語
?
前言
.Net中的運行時,以及各個類型、對象、線程堆棧以及托管堆之間的關系,在初學者(俺是初學者中的菜鳥 J)看來,有很多是難以理解的東西,俺在看了CLR Via C# 的前幾章后,現在將文中的大概意思并加以自己的理解,重現運行時,以及各個關系。希望各位盡量拍磚,多多指出不正確的地方,共同進步。
?線程堆棧的分配
?
???????? 圖1中展示了CLR加載的一個Microsoft Windows進程。在一個進程中,可能會存在多個線程。在創建一個線程時,這個線程會分配到一個1MB大小的堆棧。這個堆棧空間的作用:用于向方法傳遞 實參,并用于存儲在方法內部定義的局部變量。圖1展示了一個線程的堆棧(右側)。堆棧都是從高位內存向地位內存地址構建。在左側圖中,該線程執行了一些代 碼,它的堆棧上已經有一些數據(右圖上半部分灰色區域)。現在假定線程要執行M1方法。?
???????? 在一個方法中,應該包含一些開場白代碼,負責在方法開始前對變量進行初始化操作,以及一些收場白代碼,負責方法執行完畢之后進行清理工作,以便返回調用者。當M1方法開始執行時,它的開場白代碼在線程的對戰中為局部變量name分配內存,如圖2所示:?
接 著,M1中的代碼執行,調用M2方法,將局部變量name作為一個實參來傳遞。這造成name局部變量中的地址被壓入堆棧。在M2方法內部,將使用名為s 的形參變量來標識堆棧位置(注意,有的架構通過寄存器來傳遞實參以提升性能,但這對于當前的討論來說并不重要)。另外,在調用一個方法時,還會將一個“返 回地址”壓入堆棧。以便被調用的方法在完成之后,應該返回到這個位置。參見圖3:?
???????? M2方法開始執行時,他的開場白代碼在線程的堆棧中為局部變量length 和tally分配內存,如圖4。然后,開始執行M2方法內部的代碼。最終,M2會執行到return語句,這時CPU執行指針會被設置成堆棧中剛才存儲的[返回地址] ,而 且M2的堆棧幀會進行輾轉開解(unwind)(個人大概理解意思是:釋放M2的內部局部變量),然后堆棧內部會恢復到圖2狀態,之后,M1將繼續執行后 面代碼,最終M1也會返回到它的調用者,這個過程其實跟M2是一樣的,M1執行完成之后,M1的堆棧幀會進行輾轉開解,恢復成圖1所示那樣。跟著會執行 M1后續的代碼。圖4:?
托管堆上對象的分配
???????? 討論完了堆棧上的內存分配之后,我們再來看下托管堆上對象的分配。我們知道在.Net中值類型是存儲在堆棧上,引用類型是存儲在托管堆上,上面線程堆棧的分配中,name是string類型,屬于引用類型,string的分配屬于比較特殊的部分,這里我推薦:
Artech的大作:字符串的駐留(String Interning)
Anytao的大作:[你必須知道的.Net]第九回:品味類型—值類型與引用類型(中)—規則無邊
說明:在濤哥的這篇文章中,建議多看看精彩的評論。
???????? 現在,假定有以下兩個類定義如下:
// Employee 類定義internal class Employee{
public int GetYearEmployed(){…}
public virtual String GetProgressReport(){…}
public static Employee Lookup(String name)(){…}
}
// Manager類定義 繼承自 Employee
internal sealed class Manager:Employee{
public override String GetProgressReport(){…}
}
?? ? ? ? 現在Windows進程已經啟動,CLR已經加載完成,托管對已初始化,而且已經創建好了一個線程連同他的1MB的堆棧控件。該線程已經執行了一些代碼, 現在馬上就要調用M3代碼。圖5展示了當前的狀況。M3方法包含的代碼演示了CLR是如何工作的。我們平時不會寫這樣的代碼,因為它們實際上并不做任何有 用的事情。圖5:?
???????? 當JIT編譯器將M3的IL代碼轉換成本地CPU指令時,會注意到M3內部引用的所有類型:Employee,Int32,Manager 以及String(因為有“Joe”) 。這時,CLR要確保定義了這些類型的所有程序集已經加載到AppDomain中。然后,利用程序集的元數據,CLR提取有關這些類型的信息,并創建一些 數據結構來表示類型本身。圖6展示了用于Employee 和Manager類型對象的數據結構。由于線程之前已經執行了一些代碼,所以不妨假設int和String的類型對象已經創建好了,所以圖中沒有顯示它 們。圖6:?
現在我們來討論下這些類型對象。在創建對象的時候,所有對象除了包含實例成員外,都會再包含兩個額外的成員:類型對象指針和同步塊索引。 從上圖中可以看出,Employee和Manager類型對象都有這兩個成員。定義一個類型時,可以在類型的內部定義靜態數據字段。為這些靜態數據字段提 供支援的字節是在類型對象自身中分配的。在每個類型對象中,最后都包含一個方法表。在方法表中,類型中定義的每個方法都有一個對應的紀錄項。
Employee 定義了三個方法,所以在它的方法表中有三個紀錄項。同理,Manager只定義了一個方法,所以在Manager的方法表中只有一個紀錄項。
???????? 現在,當CLR確定方法需要的所有類型對象都已創建,而且M3的代碼已經編譯后,就允許線程開始執行M3的本地代碼。M3的開場白代碼執行時,必須從線程 的堆棧中為局部變量分配內存(引用類型存儲引用,引用指向對象所在托管堆的偏移地址,此時尚未在托管堆創建對象,所以會賦值為null,在用new新增對 象后,才會指向新對象的引用地址,值類型存儲變量本身,),在圖中代碼中,CLR會自動將所有局部變量初始化為null或0。圖7:?
???????? 然后M3繼續執行代碼,緊接著構造一個Manager對象,這個構造操作會在托管堆中創建Manager類型的一個實例,可以看出,和所有對象一 樣,Manager對象也有一個類型對象指針和同步塊索引。該對象還包含容納Manager類型定義的所有實例數據字段及其任何基類定義的所有實例字段所 需的字節。任何時候在堆上新建一個對象,CLR都會自動初始化內部類型對象指針成員,讓它引用與對象對應的類型對象(本例就是Manager類型對象)。 此外,CLR還會首先初始化同步塊索引,并將對象的所有實例字段設為null或0,然后才會調用類型構造器(可能會修改某些實力字段),隨后 new操作符會返回新建的Manager對象的內存地址,該地址保存在變量e中(e在線程的堆棧上)。圖8:?
緊 接著M3的下一行代碼調用Employee的靜態方法Lookup。在調用一個靜態方法時,CLR會定位與定義靜態方法的類型對應的類型對象。然 后,CLR在類型對象的方法表中定位引用了被調用方法的紀錄項,然后對方法進行JIT編譯(如果需要的話),最后調用JIT編譯過的代碼。就本例來說,我 們假定Employee的Lookup方法要查詢一個數據庫來查找Joe。這里Lookup是返回一個Employee類型的對象。假定數據庫指出Joe 是公司的一名經理,所以在內部,Lookup方法在堆上構造一個新的Manager對象,初始化它,然后返回該對象的地址,這個地址保存到局部變量e中。 操作的結果如圖9所示:?
???????? 注意,此時,e不再引用創建的第一個Manager對象。事實上,由于沒有變量引用這個對象,所以它是將來進行垃圾收集時的主要候選對象。
???????? M3的下一行代碼調用Employee的非虛實例方法GetYearEmployed。在調用一個非虛實例方法時,CLR會找到與發出調用的變量的的類型 對應的類型對象。在本例中,e被定義成一個Employee(假如Employee類型沒有定義這個方法,則會回溯類層次結構,在基類查找)。然 后,CLR在類型對象的方法表中找到引用了被調用方法的紀錄項,對方法進行JIT編譯,然后調用JIT編譯過的代碼,就本例來說,假定該方法返回5,這個 整數保存在year中。結果如圖10:?
???????? M3的下一行代碼調用Employee的虛實例方法GetProgressReport。在調用一個虛實例方法時,CLR要做一些額外的工作。首先,它在 發出調用的變量中查找,然后跟隨地址到發出調用的對象。在本例中,變量e指向Joe 的Manager對象。然后,CLR會檢查對象的內部類型對象指針成員,這個成員引用了對象的實際類型。然后,CLR在類型對象的方法表中定位引用了被調 用方法的紀錄項,對方法進行JIT編譯,然后調用編譯后的代碼,就本例來說,會調用Manager的GetProgressReport實現,因為e引用 的是一個Manager對象,操作結果如圖11:?
???????? 注意,如果Employee的Lookup方法發現Joe只是一個Employee,而不是一個Manager,Lookup會在內部構造一個 Employee對象,它的類型對象指針將引用Employee類型對象。這樣一類,最終執行的就是Emplouee的 GetProgressReport實現,而不是Manager的GetProgressReport實現。
???????? 至此,已經討論了源代碼、IL和JIT編譯的代碼之間的關系,還討論了線程的對戰、參數、局部變量、以及如何引用托管堆上的對象。我們還知道對象中包含一個指針,它指向對象的類型對象(類型對象中包含靜態字段、方法表、類型對象指針和同步塊索引)。還討論了CLR如何調用靜態方法、非虛方法以及虛實例方法。理解這些后,可以深刻認識到CLR的工作方式。這些知識會帶來很大的幫助。
???????? 接下來再更深層一點,看看CLR內部發生的事情。從前面幾個圖中,我們可以看到Employee和Manager創建類型對象時,必須初始化這些成員。那 么,具體初始化什么呢?CLR開始在一個進程中運行時,它會立即為System.Type類型創建一個特殊的類型對象。Employee和Manager 類型對象是該類型的實例。因此,他們的類型對象指針會初始化成Type類型對象的引用。如圖12:?
???????? 當然,System.Type類型對象本身也是一個對象,所以內部也同樣包含一個類型對象指針成員。那么這個指針指向誰呢?它指向它本身。因為System.Type類型對象本身是一個類型對象的實例。?
結束語
???????? 現在,我們總算理解了CLR的整個類型對象及其工作方式,System.Object的GetType方法返回的是存儲在指定對象的“類型對象指針”,這樣,就可以判斷系統中任何對象(包括類型對象)的真實類型。
轉載于:https://www.cnblogs.com/zzunstu/p/3408949.html
總結
以上是生活随笔為你收集整理的.Net运行时的相互关系的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 转帖:关于MongoDB你需要知道的几件
- 下一篇: IOS设计模式的六大设计原则之开放-关闭