程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> .NET對象的創建、垃圾回收、非托管資源的手動處理,.net垃圾回收

.NET對象的創建、垃圾回收、非托管資源的手動處理,.net垃圾回收

編輯:C#入門知識

.NET對象的創建、垃圾回收、非托管資源的手動處理,.net垃圾回收


本篇用來梳理對象的創建、垃圾的回收,以及非托管資源的手動處理。


→首先運行應用程序,創建一個Windows進程。

 

→CLR創建一塊連續的虛擬地址空間,這個地址空間就是托管堆。而且,這個地址空間最初並沒有對應的物理存儲空間。

 

虛擬地址空間分成2段。一個區段是普通堆,也叫GC堆,大小小於85000字節的引用類型對象的實例被分配在這裡;另一個是大對象堆,大小大於等於85000字節的引用類型對象的實例被分配在這裡。

 

對於客戶端應用程序,每個區段的大小大致是16MB;對於服務端應用程序,每個區段的大小大致是64MB。另外,每個區段的大小還會因CPU的數量,是否是32位或64位操作系統而各異。

 

隨著每個區段被裝滿對象,CLR會分配更多的區段,直到整個進程空間都滿了為止,每個進程可使用4GB的內存。

 

托管堆上維護著一個指針NextObjPtr,指向下一個對象在托管堆上分配的位置。

 

托管堆根據存儲信息的不同分為垃圾回收堆GC Heap和加載堆Loader Heap。垃圾回收堆GC Heap存儲對象實例,受GC管理;加載堆Loader Heap存儲AppDomain中的元數據信息,例如基類型、靜態字段、靜態方法、接口信息等,不受GC管理,它的生命周期從創建AppDomain開始到卸載AppDomain結束。此時,托管堆以及其它方面大致是這樣分布的:

 

 

→在托管堆上創建對象

 

□ 引用類型對象創建

 

使用new關鍵字創建引用類型對象。

FileStream fs = new FileStream(@"",FileMode.Open);

 

以上代碼經編譯器編譯,在生成的中間IL代碼中實際上是一個newobj指令。IL相關的指令還包括:ldstr指令用於創建string類型對象,newarr用於分配新的數組對象,box指令用於在值類型轉換為引用類型時,將值類型字段拷貝到托管堆上。

 

通過以下代碼來體會托管堆上創建對象的大致過程:
 

 

public class Employee
{
    private int _id;
    private Status _status;
    public Employee()
    {
        _id = 1;
        _status = new Status();
    }
}
public class Sales : Employee
{
    public bool _isLive;
    public bool IsLive()
    {
        return _isLive;
    }
    public static void Main()
    {
        Sales _sales;
        _sales = new Sales();
        _sales._isLive = true;
        Console.WriteLine(_sales.IsLive());
    }
}
public class Status
{
    private int _years;
    private char _level = "A";
}

 

1、執行Sales _sales,在線程堆棧上開辟4byte的內存空間,用於保存Sales對象的托管堆地址,此時為null。


2、_sales = new Sales(),遞歸計算Sales實例對象的字節總數,遞歸從Sales本身開始,到父類Employee,一直到基類System.object,具體字節計算過程如下:

 

Sales對象實例字節數=字段_isLive的字節數為1byte
Sales對象實例的附加成員TypeHandler字節數=4byte
Sales對象實例的附加成員SyncBlockIndex字節數=4byte
Sales對象實例的父類Employee字節數=字段_id的字節數4byte + 字段_status保存指向Status對象實例的引用為4byte

1+4+4+4+4=17byte,考慮到內存塊總是按照4byte進行內存對齊,因此補齊到20byte。

 

由於在Sales的父類Employee的構造函數中,實例化了一個Status類,所以還需要計算Status對象實例的字節數:
Status對象實例字節數=字段_years的字節數4byte + 字段_level的字節數2byte
Status對象實例的附加成員TypeHandler字節數=4byte
Status對象實例的附加成員SyncBlockIndex字節數=4byte
4+1+4+4=13byte,考慮到內存塊總是按照4byte進行內存對齊,因此補齊到16byte。

 

綜上,創建Sales對象實例所需總字節數為36。

 

3、在托管堆上向高地址擴展出36byte的連續空間,並為其分配內存地址。

 

4、進行Sales對象實例的初始化工作。

 

a:構造Sales類型的Type對象,並分配到托管堆中的Loader Heap上。
b:初始化Sales對象實例的2個附加成員:TypeHandler和SyncBlockIndex。TypeHandler指向Load Heap上的Method Table,SyncBlockIndex指向Synchronization Block內存塊,用於在多線程環境下對實例對象的同步操作。
c:實例字段的初始化,先初始化System.Object的實例字段,然後是Employee,最後是Sales的實例字段。

 

5、將Sales實例對象的托管堆地址賦值給線程棧變量_sales。


□ 值類型對象創建

 

對於值類型(不包括類中值類型實例成員),比如struct,通過new關鍵字創建,實際上是在線程棧上創建了對象,線程堆棧不受CLR的垃圾回收控制,而是由操作系統直接管理,當值類型實例所在方法結束時,其存儲單位被自動釋放。在線程棧上,操作系統維護者一個堆棧指針,指向下一個自由空間的地址。

 

線程堆棧的內存地址是由高位向低位填充,也就是入棧時由高地址向低地址擴展,出棧時由低地址向高地址刪除內存。通過以下代碼來走一遍整個過程:

 

public void SaySth()
{
    int a = 1;
    char b = 'x';
}

 

1、方法執行之前,線程堆棧指針指向一個高位,比如100
2、開始執行SaySth方法,SaySth方法的返回地址首先入棧,線程堆棧指針指向一個較低位置,比如99
3、執行int a = 1,在線程堆棧上分配4byte的內存空間,將值1保存在該地址空間內,線程堆棧指針向下移動4個字節指向一個較低位置,比如98
4、執行char b = 'x',在線程堆棧上分配2byte的內存空間,將值"x"保存在該地址空間內,線程堆棧指針向下移動4個字節指向一個較低位置,比如97
5、方法執行結束,依次上次b和a的內存,線程堆棧指針回到返回地址,即99

 

→垃圾回收

 

在托管堆上,設計了一個叫作"GC roots",這些內存空間總是可到達的,存儲著對象實例的引用,"GC roots"中的這些對象被標記為"live",所有被"GC root"引用的對象實例也被標記為"live",GC會按照這種方式一直循環遍歷下去,直到某個對象實例沒有引用對象實例。這就好像一顆"節點樹",從根節點開始遍歷,一直到沒有子節點的節點循環遍歷才結束。

 

在垃圾回收的時候,所有沒有被標記為"live"的對象實例都會回收,然後托管堆的內存空間會重新壓縮,托管堆低位的內存空間准備迎接下一批實例對象。

 

"GC root"有不同的類型:
● 當前方法內的局部變量被視作"GC root"
● 靜態變量也被視作"GC root"
● 如果一個托管對象實例需要被傳遞給COM+庫,也會被視作"GC root"
● 如果一個對象有析構器,在垃圾回收的時候是不會被回收的,只有調用finalizer的時候才對該對象進行回收,也就是說,帶有析構器的對象也被視作"GC root"

 

為了提高垃圾回收的效率,GC引入了一個"代齡"策略,將托管堆中的對象分為三代,分別為0,1和2。而且會為不同的代齡設置不同的阙值容量,第0代大約256KB,第1代大約2MB,第2代大約10MB。被新建的對象起初都是第0代,GC也總是回收第0代沒有被標記為"live"的對象實例。具體運作過程如下:

 

1、CLR初始化時,所有被添加到托管堆中的對象為第0代。
2、當有垃圾回收時,未被回收的對象代齡提升1級,變為第1代。
3、第1代對象如果沒有達到阙值容量,就不會被回收,這樣有效地提高了垃圾回收的效率。
4、僅當第0代阙值容量不足以創建新的對象,同時第1代對象實例的大小超出了第1代阙值容量,GC會同時回收第0代和第1代的對象,還未被回收的第1代對象升級為2代對象,還未被回收的第0代對象升級為1代對象。
5、如此反復。

 

→非托管資源的清理

 

對於一些托管資源,CLR的垃圾回收器能幫我們自動清理內存,而對於一些非托管資源,比如數據庫連接、文件句柄、COM對象等,仍然需要開發者手動清理。具體請參考這裡。

 

 

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved