.NET中 類型,對象,線程棧,託管堆 在運行時的關係數據結構
The Relationship at Run Time between Types,Objects,A Thread's Stack,and The Managed Heap for .NETide
by 唐小崇函數
http://www.cnblogs.com/tangchongspa
.NET中的類型,不管是值類型或引用類型都是繼承自Object的類。這點跟Java相似,但與C/C++有很大不一樣。既然值類型與引用類型都是類,那它們的沒有什麼不一樣的地方。而最值得關注的不一樣就是:值類型對象的值直接存儲在線程棧中,引用類型對象的值存放在託管堆中,它的引用存放在線程棧中。本篇博文就談談CLR的線程棧,託管堆策略。線程
當咱們執行一個.NET程序, CLR 會加載一個新的進程,這個進程可能會(按照程序的編寫需求)包含多個線程。當一個線程被建立,它會申請一個 1MB大小棧(stack)。這個棧是以高位到地位保存的。線程棧是用來保存本地變量,參數,返回地址的,稍後會有詳細解釋。同時CLR會建立一個託管堆。(因爲內容太多,先從線程棧開始講起,稍後再講託管堆)
指針
如今,讓咱們經過兩個例子方法M1,M2來了解線程棧與類型對象的關係:code
咱們先看看線程棧的樣子。圖1爲一個線程棧的示例,咱們假設該程序已經執行了一部分代碼,並即將執行M1方法。對象
圖1:線程棧示例blog
線程棧有如下做用:繼承
·保存向某個方法傳遞的參數(passing arguments);
·保存當前執行的方法內的本地變量(local variales);
·保存當前執行方法的返回地址(return address);
全部的方法都會包含一些 序幕代碼(prologue code)和 收場代碼(epilogue code) ,序幕代碼用來初始化一個方法,這些代碼把本地變量,參數,返回地址壓入線程棧中。而收場代碼會在函數執行完畢後會清理給該函數分配的空間,並將指令指針(CPU's instruction pointer)指向返回地址(即指向該函數的調用者),從而釋放線程棧空間。
當咱們開始執行M1,CLR將 本地變量name、向M2傳遞的參數s 和 返回地址 壓入線程棧中,如圖2所示:
圖2:將本地變量name、向M2傳遞的參數s和返回地址壓入線程棧中
接着咱們執行到M2(name);CLR開始調用M2方法,這時序幕代碼會在線程棧裏分配兩個本地變量 length,tally,如圖3所示。當M2方法執行完畢後,收場代碼清理M2分配的空間,並將指令指針指向返回地址(即M1)。此時,咱們的線程棧又回到圖2所示的狀態。
圖3:M2方法開始執行時線程棧的狀況
以上,就是整個線程棧的執行狀況。
咱們接着來看看CLR中的託管堆。說到託管堆,就不得不提引用類型。下面咱們列兩個示例類,Employee和它的子類Manager:
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所示:咱們如今加入一個新方法M3。當咱們的程序開始執行,CLR 會初始化線程棧和託管堆。
首先JIT編譯器將M3方法的中間代碼(IL)JIT編譯爲本地指令(native CPU instructions)。
而後CLR檢測M3引用的全部類型(本例中爲Employee,Int32,Manager,String),這時CLR會確保提供這些類型的程序集已經被加載(不然,會報錯)。
最後CLR提取有關這些類型的信息,並建立一些數據結構(Type Object)來表示的類型自己。也就是說,CLR會先建立 Type Object,而後經過它來建立類的實例對象。
圖4:當M3方法被調用。CLR在託管堆中建立對應的類的類型對象(Type Object)
在建立好的類型對象 Type Object(並非類的實例對象)中包括如下成員
類型對象指針(Type object ptr)
同步塊索引(Sync block index)
靜態成員(Statoc fieds)
方法列表(method table)
緊接着,當CLR確認M3所需求的全部的類型對象(Type Object)都被建立,則開始執行M3的本地代碼。如圖5所示,序幕代碼在線程棧中建立本地變量並將它們初始化爲null或者0。
圖5 序幕代碼在線程棧中建立本地變量
程序開始執行e = new Manager();
如圖6所示,這行代碼指示CLR在託管堆中建立一個Manager類型的實例對象(instance)。該實例對象包含: 類型對象指針、 同步塊索引以及 該類型中的定義成員(包括它的父類成員,在本例中父類爲Employee,Object)。
而後,CLR會自動將該實例對象(Manager Object)的 類型對象指針(Type object ptr)指向相應的類型對象(Manager Type Object),此外,CLR會初始化同步塊索引和全部成員爲0並調用構造器(構造函數)。
最後new 操做符將建立好的實例對象在託管堆中的地址 返回並賦值給線程棧中的引用類型變量e。
圖6:建立並初始化一個Manager實例對象
下面咱們看看3種不一樣的方法:靜態方法,非虛方法,虛方法的執行狀況。
下一行代碼是e = Employee.Lookup("Joe"); 這裏調用了Employee的靜態方法Lookup()。
如圖7所示,當調用一個靜態方法,首先JIT編譯器會定位到該方法對應的類型Type Object對象(本例中爲Employee Type Object)。而後將該Type Object函數列表中的對應方法(本例中是Lookup)JIT編譯,並執行。
咱們假設Joe存在而且是一個經理,則Lookup函數會在託管堆中建立一個Manager實例對象,並用Joe初始化它。最後將這個Manager實例對象的地址返回,賦值給e。
圖7:靜態函數Lookup被調用,建立並用joe初始化一個Manager 對象,並賦值給e
繼續執行下一行代碼:year = e.GetYearsEmployed();
如圖8所示:當咱們調用一個非虛函數(nonvirtual instance method),JIT編譯器會定位到 調用者的類型(e的類型爲Employee)對應Type Object中(本例中爲Employee Type Object)。在該Type Object中的方法列表中,查找對應方法(若是沒找到,會向其父類尋找直到Object爲止)。JIT編譯該方法,並執行。咱們不妨假設joe已經工做5年了。則Employee 的 GetYearsEmployed方法返回5,並賦值給線程棧中的year變量。
圖8:調用Employee的非虛函數 GetYearsEmployed。
接下來一行代碼是e.GetProgressReport();該方法是一個虛方法(virtual instance method)。
調用一個虛方法前,JIT編譯器會額外的執行一些代碼。這些代碼會先查找到 調用該方法的變量(e)所指向的實例對象(在本例中,即爲用Joe初始化的Manager實例對象)。接下來,檢查該對象中的類型對象指針,以找到對象的真正類型type object(本例中爲Manager Type Object)。最後JIT編譯該type object方法列表中的對應方法,並執行。
如圖9所示,JIT編譯並執行的是 Manager Type Object中的GetProgressReprot方法,而不是Employee Type Object中的。
圖9:調用虛函數GetProgerssReport(),實際類型Manager的type Object中的方法被執行
至此,示例代碼結束。咱們討論了調用靜態方法,非虛函數,虛函數的三種狀況。但咱們還有一點沒有完成。
咱們會注意到在Type Object中也有Type object ptr,這是由於這些Type Object也是一個「類型」的實例對象。它們的類型比較特殊,叫作System.Type,定義在MSCorLib.dll中。當CLR開始執行一個進程,它會先爲System.Type建立一個Type Oject,稱爲Type Type Object。如圖10所示,本例的 Employee Type Object,Manager Type Object都是Type Type Object的「實例對象」。最後,Type Type Object 自己也是一個對象,它的Type object ptr 指向自身。這就是.NET 萬物皆對象的思想。
圖10:Emloyee和Manager的type objects 是 System.Type 類型的實例對象