1. 程式人生 > >記憶體探尋1之——值型別和引用型別的記憶體分配機制

記憶體探尋1之——值型別和引用型別的記憶體分配機制

String物件和值型別的記憶體分配機制:    

      同樣由前延伸,上上篇《由String型別分析,所產生的對引數傳遞之惑的解答》中,最後提及,如果將引用型別的按值傳遞和按引用傳遞,用託管堆表示,則更具說服力。在此附圖說明:(如果印象模糊,可回看文章)

 

由上兩圖可以看到:

1.在圖1(即上面圖),當在按值傳遞引用型別引數時,接收引數的函式中(注意:依然在Stack上),依然存在一份拷貝——同樣指向原始字串("This is a String")的變數—funStr 。而此時亦即str和funStr引用都指向同一字串。然而當函式中,對funStr重賦值("This is a changed String")時,即改變了funStr的指向!但此時 原函式中的str變數的指向並未改變(如圖示),這樣才產生程式碼演示的結果——沒有改變其值;

2.在圖2(即下面圖),當按引用傳遞引用型別的引數時,其實質是 :告知編譯器傳遞引數的地址,這樣在函式體中對變數的賦值,實質上是通過原引數(str) 的指標,來改變原變數的值。在這裡表現為改變原變數的引用方向(如圖示),而對於以前的變數("This is a String")物件,由於在託管堆上,故當沒有引用時,GC(垃圾回收器)會自動清理掉。

 

由上,已經可以看到String物件的記憶體分配模式,下面簡要分析一下值型別的記憶體分配原理:

     由於值型別屬於變數和值的共同所有體,即在Stack上,某一段記憶體地址就代表著變數(ex,int i),而同時它也蘊含著它的值(ex,3)。示例圖為:

這個例子雖然簡單,但卻可以普世的代表著值型別的通用的記憶體分配。

  

自定義物件的記憶體分配機制: 

   前述對特殊物件型別String的記憶體分配做了概述,然而對自定義的物件呢?我們可以更深一步考慮:

首先:CLR管理的記憶體分配機制 

    CLR管理的記憶體,主要分為三塊,依次為:

1.執行緒的堆疊:用於分配值型別。它主要受作業系統管理,不受垃圾回收器(GC)管理。當值所屬的函式結束時,變數會自動銷燬;

2.GC堆:用於分配小物件例項(小於85000位元組);

3.LOH(Large Object Heap)用於分配大物件(超過85000位元組),一般性瞭解;

其次:自定義物件的記憶體分配機制 

眾所周知,我們建立物件的引用在Stack上,而例項則存在於Managed Heap上。然而我們需要通過下列幾個概念,進而將Managed Heap做進一步的劃分。先看在物件建立時,在物件中自動新增的附加成員:

1.TypeHandler,型別控制代碼:指向對應例項的方法表,其佔用4個位元組;

2:SyncBlockIndex,用於執行緒同步。它指向一塊被稱為同步塊的記憶體塊,管理記憶體同步,其同樣佔用4個位元組; 

3.NextObjPtr:是託管堆所維護的一個指向下一個物件例項化時的起始地址的指標;

 

有了上述概念,我們把託管堆(Managed Heap)劃分為GC堆(垃圾回收堆)Loader Heap(載入堆)。其中GC堆,不用多講,即傳統意義上的、用於存放物件例項的區域;而Loader Heap主要用於存放每個類所具有的方法表 (Method Table)(包括該類所包含的類型別、實現介面、靜態欄位,以及方法等)。其中Loader Heap不受GC控制(既不受某物件影響,顯而易見,呵呵~)。

 

有了上述鋪墊,我們大致可以講自定義物件的過程歸納為:

1.構造例項化物件中TypeHandler所指向的物件(可認為是Method Table),包括實現介面、靜態欄位、方法等,並提交至Loader Heap上; 

2. 初始化例項的2個附加成員(TypeHandler和SyncBlockIndex),並且將TypeHandler指標指向Method Table;

 3.初始化構造器,對物件欄位初始化;

 

下面通過建立一個類,並例項化物件,觀察其在記憶體上具體分配,即更加清晰,看程式碼:

複製程式碼

//Description: 通過建立類Student,演示物件的記憶體分配機制

//CopyRight: http://www.cnblogs.com/yangmingming

//Notes: 為簡便,將類的建立,和例項化類放於一起

public class Student
{
    private string studentName;
    public string StudentName
    {
        get { return studentName; }
        set { studentName = value; }
    }
    private string studentClass;
    public string StudentClass
    {
        get { return studentClass; }
        set { studentClass = value; }
    }
    private int studentAge;
    public int StudentAge
    {
        get { return studentAge; }
        set { studentAge = value; }
    }

    public void ShowStudentInfo()
    {
        Console.Write("The Student name is {0} ,class is {1},age is {3}", studentName, studentClass, studentAge);
    }

    //例項化物件為:Student s=new Student();
}

複製程式碼

此時對應的記憶體演示圖為:

說明:

1.通過圖示,可以將物件例項化時的記憶體分配充分展示;

2.方法表中,包含類中方法ShowStudentInfo、Student等,且還可以包括靜態欄位等(本例未列出);

3.方法表是依賴於類而存在的,先於物件而存在,這個順續很重要; 

 

附:(巢狀型別的說明)

 1.當值型別中巢狀引用型別:此時其中的引用變數依然建立在Stack上,而其例項化物件依然建立在GC Heap上;

2.當引用型別中巢狀值型別:此時值型別在GC Heap上建立,即不在Stack上建立; 

 

由上論述,我們基本可以窺見值型別和引用型別的記憶體分配機制,同時也對在物件呼叫方法的原理上(通過TypeHandler),有了更多瞭解;

 

下一節講述:從記憶體的角度,看繼承和多型。未完待續。。。