記憶體探尋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),有了更多瞭解;
下一節講述:從記憶體的角度,看繼承和多型。未完待續。。。