C# GC回收的方法實(shí)現(xiàn)
介紹 C# 中的 垃圾回收(Garbage Collection, GC) 機(jī)制。
?? 一、什么是 GC?
GC(Garbage Collector,垃圾回收器) 是 .NET 運(yùn)行時(shí)(CLR)自動(dòng)管理內(nèi)存的核心組件。
它的作用是:
自動(dòng)找出不再被使用的對(duì)象(“垃圾”),并釋放它們占用的內(nèi)存,防止內(nèi)存泄漏。
你不需要像 C/C++ 那樣手動(dòng) delete 或 free,C# 的 GC 會(huì)幫你搞定!
?? 二、GC 管理的是哪部分內(nèi)存?
在 C# 中,內(nèi)存主要分為兩塊:
| 內(nèi)存區(qū)域 | 存放內(nèi)容 | 是否由 GC 管理 |
|---|---|---|
| 托管堆(Managed Heap) | 引用類型對(duì)象(如 class 實(shí)例) | ? 是 |
| 棧(Stack) | 值類型(如 int, struct)、方法局部變量、引用類型的引用(指針) | ? 否(隨方法結(jié)束自動(dòng)釋放) |
?? 注意:string、數(shù)組、自定義 class 都分配在托管堆上,由 GC 負(fù)責(zé)回收。
?? 三、GC 什么時(shí)候觸發(fā)?
GC 不是實(shí)時(shí)運(yùn)行的,而是在滿足以下條件之一時(shí)自動(dòng)觸發(fā):
- 托管堆內(nèi)存不足(分配新對(duì)象時(shí)發(fā)現(xiàn)空間不夠)
- 系統(tǒng)內(nèi)存壓力大(Windows 通知 .NET 內(nèi)存緊張)
- 手動(dòng)調(diào)用 GC.Collect()(不推薦!除非特殊場(chǎng)景)
?? 開(kāi)發(fā)者通常無(wú)法精確控制 GC 何時(shí)發(fā)生,這是設(shè)計(jì)上的有意為之——讓開(kāi)發(fā)者專注業(yè)務(wù)邏輯。
??? 四、GC 如何判斷一個(gè)對(duì)象是“垃圾”?
核心原則:如果一個(gè)對(duì)象無(wú)法從“根(Root)”訪問(wèn)到,它就是垃圾。
什么是“根(Root)”?
- 全局/靜態(tài)變量
- 方法中的局部變量(包括參數(shù))
- CPU 寄存器中的引用
- 本地方法(Native code)持有的引用
示例:
void MyMethod()
{
var person = new Person(); // person 是局部變量 → 根
person.Name = "Alice";
} // 方法結(jié)束,person 超出作用域 → 不再是根
→ 此時(shí) Person 對(duì)象不可達(dá),下次 GC 時(shí)會(huì)被回收。
?? 五、GC 的核心機(jī)制:分代回收(Generational GC)
.NET 的 GC 采用 “分代回收” 策略,把對(duì)象按“年齡”分成三代:
| 代(Generation) | 特點(diǎn) | 回收頻率 |
|---|---|---|
| Gen 0 | 新創(chuàng)建的對(duì)象 | ?? 最頻繁(毫秒級(jí)) |
| Gen 1 | 活過(guò)一次 Gen 0 回收的對(duì)象 | 中等 |
| Gen 2 | 老對(duì)象(活過(guò) Gen 1) | ?? 最少(可能幾秒甚至幾分鐘一次) |
為什么分代?
經(jīng)驗(yàn)規(guī)律:大多數(shù)對(duì)象“朝生暮死”(比如臨時(shí)變量)。
所以優(yōu)先快速回收 Gen 0,避免全堆掃描,提升性能。
回收過(guò)程簡(jiǎn)述:
- GC 暫停所有線程(“Stop-The-World”)
- 從“根”出發(fā),標(biāo)記所有可達(dá)對(duì)象
- 清除未標(biāo)記對(duì)象(垃圾)
- 壓縮內(nèi)存:把存活對(duì)象往堆的一端移動(dòng),消除碎片
- 晉升:Gen 0 存活對(duì)象 → Gen 1;Gen 1 存活 → Gen 2
?? Gen 2 回收也叫 Full GC,開(kāi)銷最大,應(yīng)盡量避免頻繁發(fā)生。
?? 六、如何查看對(duì)象在哪一代?
可以使用 GC.GetGeneration(object):
var obj = new object(); Console.WriteLine(GC.GetGeneration(obj)); // 輸出 0 GC.Collect(); // 強(qiáng)制回收 Gen 0 Console.WriteLine(GC.GetGeneration(obj)); // 如果 obj 還被引用,輸出 1
??? 七、特殊對(duì)象:需要“清理”的資源
GC 只負(fù)責(zé)內(nèi)存回收,但有些對(duì)象持有非托管資源(如文件句柄、數(shù)據(jù)庫(kù)連接、網(wǎng)絡(luò) socket),這些資源不會(huì)自動(dòng)釋放!
解決方案:實(shí)現(xiàn)IDisposable+Dispose模式
public class MyFile : IDisposable
{
private FileStream _file;
public MyFile(string path)
{
_file = File.OpenRead(path);
}
public void Dispose()
{
_file?.Dispose(); // 立即釋放非托管資源
GC.SuppressFinalize(this); // 告訴 GC:不用調(diào)用析構(gòu)函數(shù)了
}
~MyFile() // 析構(gòu)函數(shù)(Finalizer)— 最后的保險(xiǎn)
{
Dispose();
}
}
// 使用方式(推薦 using)
using (var file = new MyFile("data.txt"))
{
// 使用文件
} // 自動(dòng)調(diào)用 Dispose()
? 最佳實(shí)踐:對(duì)持有非托管資源的類,務(wù)必實(shí)現(xiàn) IDisposable,并用 using 語(yǔ)句確保及時(shí)釋放。
?? 八、GC 對(duì)性能的影響 & 優(yōu)化建議
可能的問(wèn)題:
- Stop-The-World:GC 期間所有線程暫停,可能導(dǎo)致 UI 卡頓
- 頻繁分配/回收:大量臨時(shí)對(duì)象 → 頻繁 Gen 0 GC → CPU 浪費(fèi)
- 大對(duì)象堆(LOH):≥85,000 字節(jié)的對(duì)象分配在 LOH,不會(huì)壓縮,易產(chǎn)生內(nèi)存碎片
優(yōu)化建議:
| 場(chǎng)景 | 建議 |
|---|---|
| 頻繁創(chuàng)建小對(duì)象 | ?? 重用對(duì)象(對(duì)象池 ObjectPool<T>) |
| 大數(shù)組/緩沖區(qū) | ?? 使用 ArrayPool<T>.Shared |
| 長(zhǎng)時(shí)間持有對(duì)象 | ?? 避免無(wú)意中延長(zhǎng)對(duì)象生命周期(如事件訂閱未取消) |
| 高性能場(chǎng)景 | ?? 減少分配(如用 Span<T>、stackalloc) |
| 監(jiān)控 GC | ?? 使用 PerfView、dotMemory 或 GC.CollectionCount |
// 查看各代 GC 次數(shù)
Console.WriteLine($"Gen 0: {GC.CollectionCount(0)}");
Console.WriteLine($"Gen 1: {GC.CollectionCount(1)}");
Console.WriteLine($"Gen 2: {GC.CollectionCount(2)}");
?? 九、GC vs 手動(dòng)內(nèi)存管理(C++)
| 特性 | C# GC | C++ 手動(dòng)管理 |
|---|---|---|
| 內(nèi)存安全 | ? 不會(huì)野指針、重復(fù)釋放 | ? 容易出錯(cuò) |
| 開(kāi)發(fā)效率 | ? 高 | ? 低 |
| 性能可控性 | ? 較低(GC 不可預(yù)測(cè)) | ? 高 |
| 內(nèi)存碎片 | ? GC 會(huì)壓縮堆 | ? 需手動(dòng)管理 |
?? C# 的設(shè)計(jì)理念:用少量性能代價(jià),換取極高的開(kāi)發(fā)安全性和效率。
? 總結(jié):關(guān)鍵要點(diǎn)
- GC 自動(dòng)回收托管堆上的無(wú)用對(duì)象,開(kāi)發(fā)者無(wú)需手動(dòng)釋放。
- 分代回收(Gen 0/1/2) 是核心優(yōu)化策略,基于“多數(shù)對(duì)象短命”假設(shè)。
- GC 不處理非托管資源 → 必須用 IDisposable + using。
- 避免頻繁分配臨時(shí)對(duì)象,尤其在循環(huán)或高頻方法中。
- 不要隨意調(diào)用 GC.Collect() —— 通常適得其反。
- 大對(duì)象(≥85KB)進(jìn)入 LOH,不壓縮,需特別注意。
如果你正在開(kāi)發(fā)高性能應(yīng)用(如游戲、實(shí)時(shí)系統(tǒng)),理解 GC 行為至關(guān)重要;如果是普通業(yè)務(wù)系統(tǒng),只需記?。?strong>少 new 臨時(shí)對(duì)象,及時(shí) Dispose 資源,就能避開(kāi) 90% 的問(wèn)題。
到此這篇關(guān)于C# GC回收的方法實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)C# GC回收內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#(WinForm) ComboBox和ListBox添加項(xiàng)及設(shè)置默認(rèn)選擇項(xiàng)
這篇文章主要介紹了C#(WinForm) ComboBox和ListBox添加項(xiàng)及設(shè)置默認(rèn)選擇項(xiàng)的的相關(guān)資料,需要的朋友可以參考下2014-07-07
C#實(shí)現(xiàn)ASCII和字符串相互轉(zhuǎn)換的代碼示例
在C#編程語(yǔ)言中,ASCII碼是一種廣泛使用的字符編碼標(biāo)準(zhǔn),它將128個(gè)不同的字符與7位的二進(jìn)制數(shù)字對(duì)應(yīng)起來(lái),在處理文本時(shí),我們有時(shí)需要將ASCII碼與字符串進(jìn)行相互轉(zhuǎn)換,以下是如何在C#中實(shí)現(xiàn)這一操作的詳細(xì)說(shuō)明,需要的朋友可以參考下2026-01-01
C# WinForm狀態(tài)欄實(shí)時(shí)顯示當(dāng)前時(shí)間(窗體狀態(tài)欄StatusStrip示例)
這篇文章主要介紹了C# WinForm狀態(tài)欄實(shí)時(shí)顯示當(dāng)前時(shí)間(窗體狀態(tài)欄StatusStrip示例),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01
C#項(xiàng)目找不到命名空間問(wèn)題的排查記錄與解決方案
代碼從 Git 倉(cāng)庫(kù)克隆下來(lái)后,NuGet 顯示包已安裝,但編譯時(shí)大量 CS0246,PdfSharp 全部找不到,本文完整復(fù)盤問(wèn)題現(xiàn)象、原因分析與最終解決方案,供以后自己和同事快速定位,需要的朋友可以參考下2026-01-01
C#(.net)中按字節(jié)數(shù)截取字符串最后出現(xiàn)亂碼問(wèn)題的解決
這篇文章主要給大家介紹了關(guān)于C#(.net)中按字節(jié)數(shù)截取字符串最后出現(xiàn)亂碼問(wèn)題的解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-06-06
C#使用itextsharp生成PDF文件的實(shí)現(xiàn)代碼
以下是對(duì)在C#中使用itextsharp生成PDF文件的實(shí)現(xiàn)代碼進(jìn)行了詳細(xì)分析介紹,需要的朋友可以過(guò)來(lái)參考下2013-07-07
CAD2008+VS2008開(kāi)發(fā)ObjectARX加載失敗問(wèn)題(推薦)
這篇文章主要介紹了CAD2008+VS2008開(kāi)發(fā)ObjectARX加載失敗問(wèn)題,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04

