.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方法返回的是存儲在指定對象的“類型對象指針”,這樣,就可以判斷系統中任何對象(包括類型對象)的真實類型。

 

 如果覺得不錯的話,請點擊下推薦,(*^__^*) !!

轉載請注明出處:http://www.cnblogs.com/enshjiang/archive/2012/02/16/2353599.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM