[你必須知道的.NET] 第十九回:對象創建始末(下)

作者: Anytao  來源: 博客園  發布時間: 2008-09-12 15:36  閱讀: 3923 次  推薦: 1   原文鏈接   [收藏]  
 
[1] [你必須知道的.NET] 第十九回:對象創建始末(下)
[2] [你必須知道的.NET] 第十九回:對象創建始末(下)

系列文章導航:

[你必須知道的.NET] 開篇有益

[你必須知道的.NET] 第一回:恩怨情仇:is和as

[你必須知道的.NET] 第二回:對抽象編程:接口和抽象類

[你必須知道的.NET] 第三回:歷史糾葛:特性和屬性

[你必須知道的.NET] 第四回:后來居上:class和struct

[你必須知道的.NET] 第五回:深入淺出關鍵字---把new說透

[你必須知道的.NET] 第六回:深入淺出關鍵字---base和this

[你必須知道的.NET] 第七回:品味類型---從通用類型系統開始

[你必須知道的.NET] 第八回:品味類型---值類型與引用類型(上)-內存有理

[你必須知道的.NET] 第九回:品味類型---值類型與引用類型(中)-規則無邊

[你必須知道的.NET] 第十回:品味類型---值類型與引用類型(下)-應用征途

[你必須知道的.NET] 第十一回:參數之惑---傳遞的藝術(上)

[你必須知道的.NET] 第十二回:參數之惑---傳遞的藝術(下)

[你必須知道的.NET] 第十三回:從Hello, world開始認識IL

[你必須知道的.NET] 第十四回:認識IL代碼---從開始到現在

[你必須知道的.NET] 第十五回:繼承本質論

[你必須知道的.NET] 第十六回:深入淺出關鍵字---using全接觸

[你必須知道的.NET] 第十七回:貌合神離:覆寫和重載

[你必須知道的.NET] 第十八回:對象創建始末(上)

[你必須知道的.NET] 第十九回:對象創建始末(下)

[你必須知道的.NET]第二十回:學習方法論

[你必須知道的.NET]第二十一回:認識全面的null

[你必須知道的.NET]第二十二回:字符串駐留(上)---帶著問題思考

[你必須知道的.NET]第三十二回,深入.NET 4.0之,Tuple一二

 

  本文將介紹以下內容:

  • 對象的創建過程

  • 內存分配分析

  • 內存布局研究

  接上回[第十八回:對象創建始末(上)],繼續對對象創建話題的討論>>>

  2.2 托管堆的內存分配機制

  引用類型的實例分配于托管堆上,而線程棧卻是對象生命周期開始的地方。對32位處理器來說,應用程序完成進程初始化后,CLR將在進程的可用地址空間上分配一塊保留的地址空間,它是進程(每個進程可使用4GB)中可用地址空間上的一塊內存區域,但并不對應于任何物理內存,這塊地址空間即是托管堆。

  托管堆又根據存儲信息的不同劃分為多個區域,其中最重要的是垃圾回收堆(GC Heap)和加載堆(Loader Heap),GC Heap用于存儲對象實例,受GC管理;Loader Heap又分為High-Frequency Heap、Low-Frequency Heap和Stub Heap,不同的堆上又存儲不同的信息。Loader Heap最重要的信息就是元數據相關的信息,也就是Type對象,每個Type在Loader Heap上體現為一個Method Table(方法表),而Method Table中則記錄了存儲的元數據信息,例如基類型、靜態字段、實現的接口、所有的方法等等。Loader Heap不受GC控制,其生命周期為從創建到AppDomain卸載。

  在進入實際的內存分配分析之前,有必要對幾個基本概念做以交代,以便更好的在接下來的分析中展開討論。

  • TypeHandle,類型句柄,指向對應實例的方法表,每個對象創建時都包含該附加成員,并且占用4個字節的內存空間。我們知道,每個類型都對應于一個方法表,方法表創建于編譯時,主要包含了類型的特征信息、實現的接口數目、方法表的slot數目等。

  • SyncBlockIndex,用于線程同步,每個對象創建時也包含該附加成員,它指向一塊被稱為Synchronization Block的內存塊,用于管理對象同步,同樣占用4個字節的內存空間。

  • NextObjPtr,由托管堆維護的一個指針,用于標識下一個新建對象分配時在托管堆中所處的位置。CLR初始化時,NextObjPtr位于托管堆的基地址。

  因此,我們對引用類型分配過程應該有個基本的了解,由于本篇示例中FileStream類型的繼承關系相對復雜,在此本文實現一個相對簡單的類型來做說明:

Code


  將上述實例的執行過程,反編譯為IL語言可知:new關鍵字被編譯為newobj指令來完成對象創建工作,進而調用類型的構造器來完成其初始化操作,在此我們詳細的描述其執行的具體過程:

 

  • 首先,將聲明一個引用類型變量aUser:

  VIPUser aUser;

  它僅是一個引用(指針),保存在線程的堆棧上,占用4Byte的內存空間,將用于保存VIPUser對象的有效地址,其執行過程正是上文描述的在線程棧上的分配過程。此時aUser未指向任何有效的實例,因此被自行初始化為null,試圖對aUser的任何操作將拋出NullReferenceException異常。

  • 接著,通過new操作執行對象創建:

  aUser = new VIPUser();

  如上文所言,該操作對應于執行newobj指令,其執行過程又可細分為以下幾步:

  (a)CLR按照其繼承層次進行搜索,計算類型及其所有父類的字段,該搜索將一直遞歸到System.Object類型,并返回字節總數,以本例而言類型VIPUser需要的字節總數為15Byte,具體計算為:VIPUser類型本身字段isVip(bool型)為1Byte;父類User類型的字段id(Int32型)為4Byte,字段user保存了指向UserInfo型的引用,因此占4Byte,而同時還要為UserInfo分配6Byte字節的內存。

  (b)實例對象所占的字節總數還要加上對象附加成員所需的字節總數,其中附加成員包括TypeHandle和SyncBlockIndex,共計8字節(在32位CPU平臺下)。因此,需要在托管堆上分配的字節總數為23字節,而堆上的內存塊總是按照4Byte的倍數進行分配,因此本例中將分配24字節的地址空間。

  (c)CLR在當前AppDomain對應的托管堆上搜索,找到一個未使用的20字節的連續空間,并為其分配該內存地址。事實上,GC使用了非常高效的算法來滿足該請求,NextObjPtr指針只需要向前推進20個字節,并清零原NextObjPtr指針和當前NextObjPtr指針之間的字節,然后返回原NextObjPtr指針地址即可,該地址正是新創建對象的托管堆地址,也就是aUser引用指向的實例地址。而此時的NextObjPtr仍指向下一個新建對象的位置。注意,棧的分配是向低地址擴展,而堆的分配是向高地址擴展。

[第1頁][第2頁]
1
0
 
 
 

文章列表

全站熱搜
創作者介紹
創作者 大師兄 的頭像
大師兄

IT工程師數位筆記本

大師兄 發表在 痞客邦 留言(0) 人氣()