首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

.NET 深度解析:垃圾收集機制原理與代際管理(上)

     編輯:關於.NET

前言:為何 GC 是 .NET 平台的基石

構成 .NET 平台的核心組件之一,便是垃圾收集器。若無 GC,.NET 尚能稱為「平台」嗎?雖然各語言皆編譯為 MSIL,但若運行時資源回收「各自為政」,不僅大幅增加編程難度,更會導致記憶體管理複雜度失控——不同語言處理記憶體的微小差異,在回收時將被放大,亦不利於平台移植。

本文將全面介紹 .NET 垃圾收集的運行方式、核心算法,以及相關關鍵方法。

GC 歷史小檔案

鮮為人知的是,垃圾收集並非隨 Java 而生。早在 1958 年,圖靈獎得主 John McCarthy 發明的 Lisp 語言即已提供 GC 功能,這是 GC 的首次亮相;1984 年,Dave Ungar 的 Smalltalk 語言首度正式採用 GC 機制。

GC 帶來的五大價值

  1. 提高軟體系統內聚性
  2. 降低編程複雜度,開發者無需分心處理析構
  3. 不阻礙架構師進行系統抽象
  4. 減少因記憶體誤用導致的 Bug
  5. 將記憶體管理從編寫期轉移至運行期,使不可預估的管理漏洞變為可預估

第一節:GC 算法與工作方式

1. 核心算法:標記與清除

垃圾收集器的本質是追蹤所有被引用的物件,識別不再被引用的物件,並回收對應記憶體。這聽起來類似「引用計數」,但後者需遍歷所有物件並維護引用狀態,效率較低,且易因「循環引用」造成記憶體洩漏。因此 .NET 採用 標記與清除 算法。

「標記」階段:垃圾識別

從應用程式的 Root 出發,利用相互引用關係,遍歷堆上動態分配的所有物件。未被引用的物件不被標記(即為垃圾),存活物件被標記,形成一張「根-物件可達圖」。CLR 內部將物件關係視為「樹圖」,利用樹結構特性加速遍歷。

標記過程利用堆疊完成:選擇一個待檢測物件,將其所有引用壓入堆疊,反覆執行直到堆疊為空。堆疊清空意味著已遍歷該局部根可達的所有物件。樹圖節點包含區域變數、暫存器、靜態變數等,皆需重複此操作。完成後逐一檢查記憶體,未標記者即為垃圾。

「清除」階段:記憶體回收與壓縮

啟用 Compact 算法,移動存活物件並修正指標,使其在記憶體中連續分佈,閒置記憶體亦連續,解決記憶體碎片問題。新物件分配時,CLR 无需在碎片中尋找空間,分配速度大幅提升。

大物件堆(LOH)除外:GC 不會移動巨大物件,因移動開銷超過整理收益。大物件通常壽命較長,分配於堆的特殊區域。

Compact 算法的額外紅利:緊湊分配的物件常被一起使用(區域性原理),可提升 CPU 高速快取效能。

2. 代際管理

依物件存活時間分代:最短在第 0 代,最長在第 2 代(通常較大)。代數上限可透過 GC.MaxGeneration 獲取。

  • 第 0 代:新分配物件(通常 ≤256KB),按位址順序分配,多為區域變數
  • 第 1 代:經過 0 代收集仍存活(通常 ≤2MB),如表單、按鈕等 UI 物件
  • 第 2 代:經歷多次收集仍存活,通常為應用程式級物件

記憶體緊張時(如 0 代滿),GC 介入 CLR 執行引擎,優先收集 0 代(通常 <1ms)。若仍不足,遞增收集 1 代(<10ms)、2 代。收集順序非固定,視運行情況或手動呼叫 GC.Collect(i) 決定。若 2 代收集後仍不足,拋出 OutOfMemoryException。

物件若在多次 GC 後仍存活,將晉升至下一代。代的數量與容量由「策略引擎」動態調整。

實驗驗證:代際動態變化

public partial class Form1 : Form {
    private string a = new string('a', 1);
    public Form1() { InitializeComponent(); }
    private void button1_Click(object sender, EventArgs e) {
        a = new string('a', 900000);
        label1.Text = GC.GetGeneration(a).ToString();
    }
    private void Form1_Load(object sender, EventArgs e) {
        label1.Text = GC.GetGeneration(a).ToString();
    }
}

程式啟動時,a 為第 0 代。Windbg 顯示 GC 堆分兩段、三代,各代起始位址差值為 12。點擊按鈕分配大字串後,a 晉升為第 2 代,各代起始位址差值變為 98,904 與 107,908,證明代大小隨運行動態調整,GC 堆大小亦同步變化。

GC 堆初始狀態 GC 堆分配大物件後狀態
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved