天天看點

類型執行個體的建立位置、托管對象在托管堆上的結構

<a href="http://www.cnblogs.com/happyhippy/archive/2007/04/12/710929.html">http://www.cnblogs.com/happyhippy/archive/2007/04/12/710929.html</a>

1. 值類型執行個體的建立位置: 

    對于值類型的執行個體,CLR在運作時有兩種配置設定方式:(1) 如果該值類型的執行個體作為類型中的方法(Method)中的局部變量,則該執行個體被建立線上程棧上;(2) 如果該值類型的執行個體作為類型的成員,則該執行個體作為引用類型(引用類型在GC堆或者LOH上建立)的執行個體的一部分,被建立在GC堆上。下面這段代碼示範了這兩種情況:

類型執行個體的建立位置、托管對象在托管堆上的結構

public class Test1 

類型執行個體的建立位置、托管對象在托管堆上的結構

類型執行個體的建立位置、托管對象在托管堆上的結構

 private int i;//上面(2)中的情況,生成Test的執行個體的同時,int類型的執行個體i被建立在GC堆上 

類型執行個體的建立位置、托管對象在托管堆上的結構

 public Test1() 

類型執行個體的建立位置、托管對象在托管堆上的結構

 { 

類型執行個體的建立位置、托管對象在托管堆上的結構

 byte b =0;//(1)中的情況,byte類型的執行個體b被建立在執行這段代碼的線程棧上 

類型執行個體的建立位置、托管對象在托管堆上的結構

    } 

類型執行個體的建立位置、托管對象在托管堆上的結構

}

2. 引用類型執行個體的建立位置: 

    對于引用類型的執行個體,CLR在運作時也有兩種配置設定方式:(1) 如果該引用類型的執行個體的Size&lt;85000Byte,則該執行個體被建立在GC(Garbage Collection)堆上(當CLR在配置設定和回收對象時,GC可能會對GC堆進行壓縮);(2) 如果該引用類型的執行個體的Size&gt;=85000byte,則該執行個體被建立在LOH(Large Object Heap)上(LOH不會被壓縮)。面這段代碼示範了這兩種情況:

類型執行個體的建立位置、托管對象在托管堆上的結構

public class Test2 

類型執行個體的建立位置、托管對象在托管堆上的結構
類型執行個體的建立位置、托管對象在托管堆上的結構

 private int[] intArr; 

類型執行個體的建立位置、托管對象在托管堆上的結構

 public Test2() 

類型執行個體的建立位置、托管對象在托管堆上的結構
類型執行個體的建立位置、托管對象在托管堆上的結構

 private Object o = new Object();//引用o存線上程棧上,它指向GC堆上的Object執行個體 

類型執行個體的建立位置、托管對象在托管堆上的結構

        intArr = new int[21250];//符合(2)中的Size條件,int數組的執行個體被建立在LOH上 

類型執行個體的建立位置、托管對象在托管堆上的結構
類型執行個體的建立位置、托管對象在托管堆上的結構

3. 托管對象被引用的七種途徑: 

    上面的代碼片段Test2中,也示範了引用托管對象的兩種途徑: 

(1) intArr随Test2執行個體建立的同時,被建立在GC堆上,由GC堆上的類型(Test2)執行個體持有托管對象(int數組的執行個體)的引用; 

(2) 引用類型的變量o存線上程棧上,由線程棧上的局部變量(o)持有托管對象(Object執行個體)的引用。 

其實還有以下五種途徑可以持有托管對象的引用: 

(3) LOH堆上的執行個體(原理同1); 

(4) 在與非托管語句互動操作或者P/Invoke情況下的句柄表(Handle Table); 

(5) 寄存器,例如執行執行個體方法時的this指針和方法參數(IL中的call、callvirl指令是将函數參數進行自右往左的壓棧處理通過棧實作傳遞參數;而 fastcall指令則可以最多将兩個參數分别儲存到ECX和EDX兩個寄存器中,通過寄存器來實作參數傳遞,以提高程式的性能;這裡的方法參數是指後一種fastcall的情況); 

(6) 擁有終結器(finalizer)方法的對象的終結器隊列; 

(7) 所屬類型的HandleTable(對象建立時,該HandleTable将持有一個弱引用Weak Reference)。下圖示範了這幾種情況: 

4. 托管對象的結構: 

    從上圖中,我們可以看到,托管對象的引用并不是指向對象的起始位置,而是相對起始位置有+4Byte(DWord)的偏移量,這4個Byte稱為對象頭。下面轉載一段對該對象頭的介紹:對象頭儲存一個間接指向SyncTableEntry表的索引(從1開始計數的syncblk編号)。 SyncTableEntry維護一個反向的弱引用,以便CLR可以跟蹤SyncBlock的所有權。弱引用讓GC可以在沒有其它強引用存在時回收對象。 SyncTableEntry還儲存了一個指向SyncBlock的指針,包含了很少需要被一個對象的所有執行個體使用的有用的資訊。這些資訊包括對象鎖,哈希編碼,任何轉換層(thunking)資料和應用程式域的索引。對于大多數的對象執行個體,不會為實際的SyncBlock配置設定記憶體,而且syncblk編号為0。這一點在執行線程遇到如lock(obj)或者obj.GetHashCode的語句時會發生變化,如下所示:

1

類型執行個體的建立位置、托管對象在托管堆上的結構

Object obj = new Object(); 

2

類型執行個體的建立位置、托管對象在托管堆上的結構

lock(obj) { /* Do some synchronized work here */ } 

3

類型執行個體的建立位置、托管對象在托管堆上的結構

obj.GetHashCode();

    在以上代碼中,smallObj會使用0作為它的起始的syncblk編号。lock語句使得CLR建立一個syncblk入口并使用相應的數值更新對象頭。因為C#的lock關鍵字會擴充為try-finally語句并使用Monitor類,一個用作同步的Monitor對象在syncblk上建立。堆 GetHashCode的調用會使用對象的哈希編碼增加syncblk。在SyncBlock中有其它的域,它們在COM互動操作和封送委托 (marshaling delegates)到非托管代碼時使用,不過這和典型的對象用處無關。

    緊跟在syncblk編号後的是一個TypeHandle句柄(占4個byte),有點像C++中的虛方法表指針,但實際上這個TypeHandle指向的MethodTable比C++中的虛方法表複雜得多(這裡先不介紹,以後有機會再介紹)。

    在TypeHandle之後才開始存放執行個體字段的執行個體。預設情況下(即相當于在類上運用特性StructLayoutAttribute(LayoutKind.Auto)),執行個體字段會以記憶體最有效使用的方式排列,以便更有效地使用記憶體,排序之後字段在記憶體中的布局順序跟字段在類中聲明的順序不一定相同,這樣也給我們計算托管對象的Size帶來了不便....

   &lt;&amp;lt;未完待續&gt;&amp;gt;下一篇将繼續讨論如何獲得托管對象的Size。

本文轉自cnn23711151CTO部落格,原文連結:http://blog.51cto.com/cnn237111/618293 ,如需轉載請自行聯系原作者