目前項目開發基本都基於.NetCore 3.1以上了,有些老版本的規則和概念也沒有列出來,低版本的垃圾回收類型和內存釋放方式會有所不同
垃圾回收器為什么存在
-
開發人員不必手動釋放內存。
-
有效分配托管堆上的對象。
-
回收不再使用的對象,清除它們的內存,並保留內存以用於將來分配。 托管對象會自動獲取干凈的內容來開始,因此,它們的構造函數不必對每個數據字段進行初始化。
-
通過確保對象不能使用另一個對象的內容來提供內存安全。
托管堆代數
概述
為優化垃圾回收器的性能,將托管堆分為三代:第 0 代、第 1 代和第 2 代。目的是為了單獨處理短生存期對象和長生存期對象。垃圾回收器大部分時間都在處理短生存期對象的回收。
底層一代的GC回收會觸發年輕一代的GC回收,第二代的GC回收會觸發完整的GC回收.
第0代(暫時代) | 第1代(暫時代) | 第2代 | LOH(邏輯第3代) | |
---|---|---|---|---|
所處內存段 | 暫時段 | 暫時段 | 非暫時段 | 非暫時段 LOH(大型對象堆)實際位於第二代 單獨在第二代上為其划分了一塊區域。 邏輯上稱為第3代 |
包含 | 短生存期對象,即新分配的對象 | 短生存期對象,從第0代回收后, 未被回收的對象升級為第1代。 |
長生存期的對象,第一代回收后, 未被回收的對象升級為第2代。 |
對象的大小>= 85,000 字節 |
回收條件 | 第0代已分配內存達到閾值 如果第0代已滿,仍嘗試創建新對象 調用GC.Collect()方法 第1代GC回收 |
第1代已分配內存達到閾值 第0代回收之后仍然沒有足夠的空間存放新對象(此時會先回收第1代,再回收第2代) 調用GC.Collect方法 第2代GC回收 |
第2代已分配內存達到閾值 第0代回收之后仍然沒有足夠的空間存放新對象(此時會先回收第1代,再回收第2代) 調用GC.Collect方法 達到LOH回收條件 系統內存不足 |
達到第2代回收條件 大型對象內存分配達到閾值 |
回收方式 | 前台垃圾回收,當前托管線程被掛起 | 前台垃圾回收,當前托管線程被掛起 | 后台垃圾回收,當前托管線程正常執行 | 同第二代 |
想要判斷一個對象是否為大對象,可通過以下代碼查看
var o = new Byte[85000];
Console.WriteLine(GC.GetGeneration(o));//GC2,大對象
o = new Byte[84900];
Console.WriteLine(GC.GetGeneration(o)); //GC0,小對象 84999仍是大對象,需要用一定量的內存空間保存指針
var arr = new int[85000 / 4];
Console.WriteLine(GC.GetGeneration(arr));//GC2,大對象,數組會提前開辟空間, int占32位,4個字節,85000 / 4加上指針內容會達到大對象的大小
arr = new int[85000 / 4 - 20];
Console.WriteLine(GC.GetGeneration(arr));//GC0,小對象
閾值
當垃圾回收器檢測到某個代中的幸存率很高時,它會增加該代的分配閾值,避免垃圾回收過於頻繁地運行
但是閾值調大之后,會導致一次回收的內存過高。
所以閾值由CLR動態決定,以調節 回收頻率和單次回收內存大小的平衡
垃圾回收類型
工作站(默認方式) | 服務器 | |
---|---|---|
特點 | 垃圾回收線程同用戶線程優先級相同,會與用戶線程爭用CPU資源 只有一個處理器的計算機無論是否修改配置文件最終都會應用工作站垃圾回收方式 |
有垃圾回收的專用線程 線程優先級為THREAD_PRIORITY_HIGHEST 每個CPU都會分配一個垃圾回收專用線程和專用堆。不同的堆可以互通 多個垃圾回收線程一起工作,所以堆大小相同時,服務器垃圾回收比工作站垃圾回收快 |
適用場景 | 普通場景 | 需要高吞吐量和可伸縮性的服務器應用程序 |
內存釋放
釋放目標
GC釋放應用程序不再使用的對象的內存,通過檢查應用程序的根來確定不再使用的對象
應用程序的根包括:靜態字段、局部變量、CPU 寄存器、GC 句柄和終結隊列
釋放步驟
- 列出不可訪問對象和幸存對象的地址塊並**標記**
- 使用內存復制功能壓縮可以訪問的對象到不可訪問的地址塊中,就是把存活下來的對象重新排列到連續的內存塊中
- 大對象通常不會壓縮,因為大對象所占用的內存區域過大,移動成本太大
- 回收死空間
- 指針更正,讓對象指針指向新地址,指針更正是因為壓縮了對象,對象在內存中的位置發生了變化
代碼調優
- 始終調用引用對象的Dispose方法,始終在實現了IDisposable的類中正確實現析構函數
- 靜態類中分配的對象不再使用后及時刪除
- 禁止在IOC聲明為單例生命周期的類中注入瞬時生命周期的對象
- 非必要時不要創建大型對象
- 可視情況用ValueTask
來代替Task ,Task為引用類型,cpu密集型的調用會頻繁觸發第0代的GC回收 - 盡可能重復使用HttpClient
- 使用ArrayPool或MemoryPool從緩沖池中租用對象空間
- 使用弱引用WeakReference重復使用已不再使用但尚未被回收的對象
監控及調試
-
監聽垃圾回收 ETW 事件,可用PerfView查看ETW事件,適用於window平台。也可在代碼中引入
Microsoft.Diagnostics.Tracing.TraceEvent
nuget包在代碼中監聽指定的GC回收等事件自定義后續處理邏輯 -
使用性能監視器Perfmon.exe,適用於windows平台
-
使用SOS調試,抓取dump轉儲文件后用WinDbg進行分析診斷,適用范圍較廣,可看到最全的內存信息
-
.Net CLI工具dotnet-counters,可以看到大概的性能指標數據統計結果,適用於臨時運行狀況查看和監視