Maoni Stephens 是 .NET 垃圾回收器 (GC) 的首席架構師之一,她在2023年8月份發表了一篇關于 .NET GC 新功能的博客文章,該功能稱為 Dynamic Adaption To Application Sizes (DATAS),該功能將隨 .NET 8 一起提供。此功能將在應用運行時自動增加或減少服務器 GC 模式下的托管堆數量。它減少了 .NET 應用使用的內存總量,使服務器 GC 模式成為內存受限環境(如 Docker 容器或 Kubernetes Pod)的可行選項,這些環境可以訪問多個邏輯 CPU 內核。
服務器 GC 模式和工作站 GC 模式之間的差異
工作站模式最初是為客戶端應用程序設計的。過去,執行應用代碼的線程會停止,直到 GC 運行完成。在桌面應用程序中,您不希望在幾毫秒甚至幾秒鐘內出現凍結,因此 Workstation GC 經過調整,可以更頻繁地執行運行,并更快地完成單個運行。從 .NET Framework 4.0 開始,我們還具有后臺 GC 運行模式,可最大程度地減少線程被阻塞的時間。
相比之下,服務器 GC 旨在最大限度地提高服務的吞吐量,這些服務將隨著時間的推移接收短期請求。GC 運行頻率較低,但可能需要更長的時間。最后,您將在 GC 上運行上花費更少的時間,而將更多的時間花在服務代碼上。
最明顯的區別如下:Workstation GC 僅使用單個托管堆。托管堆由以下子堆組成:
- 小對象堆 (SOH) 及其三代 0、1 和 2。小于 85,000 字節的對象將在此處分配。
- 大型對象堆 (LOH),用于大于或等于 85,000 字節的對象。
- 固定對象堆 (POH),主要由為此執行互操作和固定緩沖區的庫使用(例如,用于網絡或其他 I/O 方案)。
在服務器 GC 模式下,您將擁有多個這樣的托管堆,默認情況下每個邏輯 CPU 內核一個,但這可以通過 GCHeapCount 進行調整。
托管堆數量增加,以及 GC 運行執行頻率較低,是解釋為什么服務器 GC 模式下內存消耗要高得多的重要因素。
但是,如果您希望從服務器 GC 模式中受益,同時在運行時動態調整托管堆的數量,該怎么辦?一個典型的方案是在云中運行的服務,它必須在特定的突發時間處理大量請求,但之后它應該縮減以減少內存消耗。到目前為止,除了使用不同的配置值重新啟動服務外,您沒有辦法實現這一點。縱向擴展也需要重新啟動,因此許多開發團隊只是試圖通過 GCHeapCount 和 ConserveMemory 選項找到折衷方案。
這時,.NET 8 帶來了一項名為“動態適應應用程序大小”(DATAS) 的新功能就派上用場了。DATAS 在運行時將按以下方式運行:
- GC 將僅從單個托管堆開始。
- 根據稱為“吞吐量成本百分比”的指標,GC 將決定增加托管堆的數量是否可行。這將在每三次 GC 運行時進行評估。
- 還有一個稱為“空間成本”的指標,GC 使用它來決定是否應該減少托管堆的數量。
- 如果 GC 決定增加或減少托管堆的數量,它將阻塞您的線程(類似于壓縮 GC 運行)并創建或刪除托管堆。相應的內存區域將被移動。當涉及到托管堆中內存的內部組織時,在 .NET 6 和 .NET 7 中從段切換到區域,使此方案成為可能。
優點和缺點?
DATAS 允許在內存受限環境中使用服務器 GC 模式,例如在 Docker 容器、Kubernetes Pod 。在您的服務將受到大量請求的攻擊突發期間,GC 將動態增加托管堆的數量,以便從服務器 GC 的優化吞吐量設置中受益。突發結束后,GC 將再次減少托管堆的數量,從而減少應用使用的內存總量。即使在突發期間,GC 也可能選擇將托管堆增加到每個邏輯 CPU 內核少于 1 個,因此您最終可能會使用更少的內存,而無需手動配置托管堆的數量。
請記?。寒攽弥挥幸粋€邏輯 CPU 內核可用時,應始終使用 Workstation GC 模式。僅當應用有兩個或更多可用內核時,服務器 GC 模式才有用。此外,我建議您驗證您是否確實需要服務器 GC 模式。使用 K6 或 NBomber 等工具來衡量 Web 應用的吞吐量。如果仔細設計了應用的內存使用情況,則吞吐量可能根本沒有差異。永遠記?。?NET GC 只會在分配內存時執行其運行。
DATAS 是一項很棒的新功能,它將 Workstation GC 和 Server GC 的優勢結合在一起:您開始時內存更少,當請求激增時,GC 可以動態擴展其托管堆的數量以提高吞吐量。當請求數在以后的某個時間點減少時,也可以減少托管堆的數量以釋放內存。
DATAS 可以在.NET 8 產品中使用,但是并沒有默認啟用,需要手動進行指定:若要試用 DATAS,需要安裝 .NET 8 SDK,創建一個 .NET 8 應用(例如 ASP.NET Core),然后可以將以下兩行添加到 .csproj 文件:
<PropertyGroup>
???? <ServerGarbageCollection>true</ServerGarbageCollection>
???? <GarbageCollectionAdapatationMode>1</GarbageCollectionAdapatationMode>
</PropertyGroup>
您還可以在構建項目時通過命令行參數指定它:
dotnet build /p:ServerGarbageCollection=true /p:GarbageCollectionAdapatationMode=1
或者在 runtimeconfig.json 中:
"configProperties": {
???? "System.GC.Server": true,
???? "System.GC.DynamicAdaptationMode": 1
}
或者通過環境變量:
set DOTNET_gcServer=1
set DOTNET_GCDynamicAdaptationMode=1
請記?。菏褂蒙鲜龇椒ㄖ粫r,不得設置 GCHeapCount 選項。如果這樣做,GC 將只使用指定數量的堆,而不會激活 DATAS。同樣重要的是:如果要在工作站模式下運行,只需將 ServerGarbageCollection 或相應的配置屬性/環境變量分別設置為 false 或零。
默認情況下,我的 ASP.NET Core 應用將使用哪種 GC 模式?
你的 ASP.NET Core 應用可以訪問多少個邏輯 CPU 內核?如果小于兩個,則將使用 Workstation GC 模式。否則,默認情況下將激活服務器 GC 模式。因此,在 Docker、Kubernetes 或云環境中為應用指定約束時要特別小心,因為這些環境可能會突然進入另一個 GC 模式,占用的內存比預期的要多。