CLR的GC工作模式介紹(Workstation和Server)

志存高遠,腳踏實地發表於2022-03-12

CLR的核心功能之一就是垃圾回收(garbage collection),關於GC的基本概念本文不在贅述。這裡主要針對GC的兩種工作模式展開討論和研究。

Workstaction模式介紹

該模式設計的目的是用於客戶端類的應用(Client),這類應用的部署特點是同一臺機器會部署很多應用程式,並且這些應用程式的效能要求並沒有伺服器程式(nginx、asp.net等)那麼高。那麼在此種場景下,GC做了哪些設計和調整呢?

  1. 首先,最顯著的特點是當觸發垃圾回收時,CLR並沒有安排專用的執行緒來執行垃圾回收操作,僅僅是使用當前觸發GC的執行緒去執行後續操作,並且連執行緒優先順序都沒有提升。那麼自然GC操作就要和其它使用者執行緒來平等競爭CPU使用。
  2. 其次,GC堆的數量上也僅僅只有1組(1個大物件堆、1個小物件堆),這樣可以更少的佔用記憶體空間。

Server模式介紹

顧名思義,Server模式就是針對伺服器場景設計的,此類應用的部署特點是伺服器記憶體大CPU核心多,部署的程式數量少(基本都是專機專用),但是對程式效能要求非常高。於是這種模式下,GC的也做了相應的調整:

  1. 基於CPU核心多的特點,CLR則分配專用的執行緒(按照CPU核心數分配)來執行GC操作,且專用執行緒的優先順序為THREAD_PRIORITY_HIGHEST,也比普通託管執行緒更高。這樣當需要執行GC時可以保證優先佔用CPU,從而儘快完成GC操作,儘快滿足記憶體申請所需空間。
  2. 基於記憶體大的特點,那就多分配GC堆,且每個堆更大,從而避免頻繁的觸發GC。具體來說,GC堆數量是根據CPU核心數來分配,比如8核CPU就分配8組GC堆。至於堆的預設大小,將按照如下規則分配:

CLR的GC工作模式介紹(Workstation和Server)

注意上表的值是每個堆的大小,對多核CPU來說,Server模式的GC堆分配空間顯然是大於Wordstation模式的。

什麼是後臺垃圾回收(Background)?

我們知道CLR根據GC次數把物件分為0代、1代、2代。其中01代執行的頻率高、耗時短,也被稱為ephemeral generations。 根據CLR的設計,執行01代垃圾回收時,所有託管執行緒是被短暫掛起,直到垃圾回收結束後再恢復執行。但是執行2代的垃圾回收會耗時相對更長,如果也掛起全部執行緒,無疑會對程式響應造成較大的影響。

因此CLR提出單獨分配專用執行緒,用來執行2代垃圾回收,且此執行緒執行時不會掛起其它執行緒,從而降低對程式的影響。注意此處2代垃圾回收其實是包含了之前的01代的,只不過01代執行很短暫且01代執行時會依舊會掛起全部執行緒。

關於Background專用執行緒的數量,workstation模式擁有1個專用執行緒,server模式分配和邏輯處理器數量一致的專用執行緒。

在workstation模式下background回收的執行示意圖:

後臺工作站垃圾回收

上圖的解讀:

  1. GC THREAD就是background專用執行緒,可以看到其執行期間,其它執行緒沒有影響。
  2. 其中綠色箭頭代表01代的回收操作,可以看到其執行就是在普通的THREAD1執行緒,而且掛起了其它所有執行緒。

在server模式下background回收的執行示意圖:

後臺伺服器垃圾回收

上圖解讀:

  1. GC THREAD1/2代表01代的專用執行緒,BGC THREAD1/2代表background專用執行緒。
  2. 同樣的background執行緒執行回收時,其它執行緒不受影響。
  3. 綠色箭頭代表01代的回收操作,可以看到掛起了其它所有執行緒。

需要額外說明是,background是最新的叫法,之前版本稱為concurrent。他們之間的升級關係如下:

  workstation模式 server模式
.NET Framework 4之前 concurrent gc 不支援
.NET Framework 4 background gc 不支援
.NET Framework 4.5及之後 background gc background gc

二個模式的對比

  workstation模式 server模式 說明
專用執行緒 有單獨的專用執行緒,且數量等於CPU核心數 server模式充分利用多核特性,最大化保證快速完成GC操作。
gc執行的優先順序 普通 THREAD_PRIORITY_HIGHEST workstation模式執行緒執行GC操作需要和普通業務執行緒平等競爭CPU,GC不能最快的完成。
gc堆數量 1 多個,數量等於CPU核心數

workstation模式堆數量少且記憶體更少,但是容易引起GC。

server模式堆數量多佔用記憶體更多,可以降低觸發GC的次數,進而提高程式的響應效能。

background支援情況 支援,但只會分配1個background專用執行緒 支援,分配的background專用執行緒數量等於CPU核心數

 

怎麼設定不同工作模式?

專案檔案csproj中設定:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <ServerGarbageCollection>true</ServerGarbageCollection>
    <ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
  </PropertyGroup>
</Project>

runtimeconfig.json 檔案設定:

{
    "runtimeOptions":{
        "configProperties":{
            "System.GC.Server":true,
            "System.GC.Concurrent":true
        }
    }
}

單獨說明一點,對於單核CPU的計算機,無論如何配置都是workstation模式,當然現代計算機這種情況極少了。

通過dump驗證上述機制

對於workstation模式的程式程式,通過dump檢視gc堆資訊和gc模式資訊分別如下:

0:004> !eeversion
6.0.21.52210 free
Workstation mode
SOS Version: 6.0.5.7301 retail build


0:004>  !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x0000024E80001030
generation 1 starts at 0x0000024E80001018
generation 2 starts at 0x0000024E80001000
ephemeral segment allocation context: none
         segment             begin         allocated         committed    allocated size    committed size
0000024E80000000  0000024E80001000  0000024E8010DFE8  0000024E80112000  0x10cfe8(1101800)  0x111000(1118208)
Large object heap starts at 0x0000024E90001000
         segment             begin         allocated         committed    allocated size    committed size
0000024E90000000  0000024E90001000  0000024E9004FD40  0000024E90050000  0x4ed40(322880)  0x4f000(323584)
Pinned object heap starts at 0x0000024E98001000
0000024E98000000  0000024E98001000  0000024E98006C50  0000024E98012000  0x5c50(23632)  0x11000(69632)
Total Allocated Size:              Size: 0x161978 (1448312) bytes.
Total Committed Size:              Size: 0x160000 (1441792) bytes.
------------------------------
GC Allocated Heap Size:    Size: 0x161978 (1448312) bytes.
GC Committed Heap Size:    Size: 0x160000 (1441792) bytes.

對於server模式的程式程式,通過dump檢視gc堆資訊和gc模式資訊分別如下:

檢視程式碼
0:012> !eeversion
6.0.21.52210 free
Server mode with 8 gc heaps
SOS Version: 6.0.5.7301 retail build


0:015> !eeheap -gc
Number of GC Heaps: 8
------------------------------
Heap 0 (000001E88D3F3AD0)
generation 0 starts at 0x000001E88EF51030
generation 1 starts at 0x000001E88EF51018
generation 2 starts at 0x000001E88EF51000
ephemeral segment allocation context: none
         segment             begin         allocated         committed    allocated size    committed size
000001E88EF50000  000001E88EF51000  000001E88EF51048  000001E88EF52000  0x48(72)  0x1000(4096)
Large object heap starts at 0x000001EC8EF51000
         segment             begin         allocated         committed    allocated size    committed size
000001EC8EF50000  000001EC8EF51000  000001EC8EF51018  000001EC8EF52000  0x18(24)  0x1000(4096)
Pinned object heap starts at 0x000001ED0EF51000
000001ED0EF50000  000001ED0EF51000  000001ED0EF53010  000001ED0EF62000  0x2010(8208)  0x11000(69632)
Allocated Heap Size:       Size: 0x2070 (8304) bytes.
Committed Heap Size:       Size: 0x2000 (8192) bytes.
------------------------------
Heap 1 (000001E88D41F830)
generation 0 starts at 0x000001E90EF51030
generation 1 starts at 0x000001E90EF51018
generation 2 starts at 0x000001E90EF51000
ephemeral segment allocation context: none
         segment             begin         allocated         committed    allocated size    committed size
000001E90EF50000  000001E90EF51000  000001E90EF51048  000001E90EF52000  0x48(72)  0x1000(4096)
Large object heap starts at 0x000001EC9EF51000
         segment             begin         allocated         committed    allocated size    committed size
000001EC9EF50000  000001EC9EF51000  000001EC9EF51018  000001EC9EF52000  0x18(24)  0x1000(4096)
Pinned object heap starts at 0x000001ED1EF51000
000001ED1EF50000  000001ED1EF51000  000001ED1EF51018  000001ED1EF52000  0x18(24)  0x1000(4096)
Allocated Heap Size:       Size: 0x78 (120) bytes.
Committed Heap Size:       Size: 0x2000 (8192) bytes.
------------------------------
Heap 2 (000001E88D426EE0)
generation 0 starts at 0x000001E98EF51030
generation 1 starts at 0x000001E98EF51018
generation 2 starts at 0x000001E98EF51000
ephemeral segment allocation context: none
         segment             begin         allocated         committed    allocated size    committed size
000001E98EF50000  000001E98EF51000  000001E98EF51048  000001E98EF52000  0x48(72)  0x1000(4096)
Large object heap starts at 0x000001ECAEF51000
         segment             begin         allocated         committed    allocated size    committed size
000001ECAEF50000  000001ECAEF51000  000001ECAEF51018  000001ECAEF52000  0x18(24)  0x1000(4096)
Pinned object heap starts at 0x000001ED2EF51000
000001ED2EF50000  000001ED2EF51000  000001ED2EF51430  000001ED2EF52000  0x430(1072)  0x1000(4096)
Allocated Heap Size:       Size: 0x490 (1168) bytes.
Committed Heap Size:       Size: 0x2000 (8192) bytes.
------------------------------
Heap 3 (000001ED9B54AFB0)
generation 0 starts at 0x000001EA0EF51030
generation 1 starts at 0x000001EA0EF51018
generation 2 starts at 0x000001EA0EF51000
ephemeral segment allocation context: none
         segment             begin         allocated         committed    allocated size    committed size
000001EA0EF50000  000001EA0EF51000  000001EA0EF51048  000001EA0EF52000  0x48(72)  0x1000(4096)
Large object heap starts at 0x000001ECBEF51000
         segment             begin         allocated         committed    allocated size    committed size
000001ECBEF50000  000001ECBEF51000  000001ECBEF51018  000001ECBEF52000  0x18(24)  0x1000(4096)
Pinned object heap starts at 0x000001ED3EF51000
000001ED3EF50000  000001ED3EF51000  000001ED3EF51018  000001ED3EF52000  0x18(24)  0x1000(4096)
Allocated Heap Size:       Size: 0x78 (120) bytes.
Committed Heap Size:       Size: 0x2000 (8192) bytes.
------------------------------
Heap 4 (000001ED9B576570)
generation 0 starts at 0x000001EA8EF51030
generation 1 starts at 0x000001EA8EF51018
generation 2 starts at 0x000001EA8EF51000
ephemeral segment allocation context: none
         segment             begin         allocated         committed    allocated size    committed size
000001EA8EF50000  000001EA8EF51000  000001EA8EF51048  000001EA8EF52000  0x48(72)  0x1000(4096)
Large object heap starts at 0x000001ECCEF51000
         segment             begin         allocated         committed    allocated size    committed size
000001ECCEF50000  000001ECCEF51000  000001ECCEF51018  000001ECCEF52000  0x18(24)  0x1000(4096)
Pinned object heap starts at 0x000001ED4EF51000
000001ED4EF50000  000001ED4EF51000  000001ED4EF51018  000001ED4EF52000  0x18(24)  0x1000(4096)
Allocated Heap Size:       Size: 0x78 (120) bytes.
Committed Heap Size:       Size: 0x2000 (8192) bytes.
------------------------------
Heap 5 (000001ED9B5A2420)
generation 0 starts at 0x000001EB0EF51030
generation 1 starts at 0x000001EB0EF51018
generation 2 starts at 0x000001EB0EF51000
ephemeral segment allocation context: none
         segment             begin         allocated         committed    allocated size    committed size
000001EB0EF50000  000001EB0EF51000  000001EB0EF673D8  000001EB0EF72000  0x163d8(91096)  0x21000(135168)
Large object heap starts at 0x000001ECDEF51000
         segment             begin         allocated         committed    allocated size    committed size
000001ECDEF50000  000001ECDEF51000  000001ECDEF51018  000001ECDEF52000  0x18(24)  0x1000(4096)
Pinned object heap starts at 0x000001ED5EF51000
000001ED5EF50000  000001ED5EF51000  000001ED5EF53010  000001ED5EF62000  0x2010(8208)  0x11000(69632)
Allocated Heap Size:       Size: 0x18400 (99328) bytes.
Committed Heap Size:       Size: 0x22000 (139264) bytes.
------------------------------
Heap 6 (000001ED9B5CE2D0)
generation 0 starts at 0x000001EB8EF51030
generation 1 starts at 0x000001EB8EF51018
generation 2 starts at 0x000001EB8EF51000
ephemeral segment allocation context: none
         segment             begin         allocated         committed    allocated size    committed size
000001EB8EF50000  000001EB8EF51000  000001EB8EF51048  000001EB8EF52000  0x48(72)  0x1000(4096)
Large object heap starts at 0x000001ECEEF51000
         segment             begin         allocated         committed    allocated size    committed size
000001ECEEF50000  000001ECEEF51000  000001ECEEF51018  000001ECEEF52000  0x18(24)  0x1000(4096)
Pinned object heap starts at 0x000001ED6EF51000
000001ED6EF50000  000001ED6EF51000  000001ED6EF51018  000001ED6EF52000  0x18(24)  0x1000(4096)
Allocated Heap Size:       Size: 0x78 (120) bytes.
Committed Heap Size:       Size: 0x2000 (8192) bytes.
------------------------------
Heap 7 (000001ED9B5FA180)
generation 0 starts at 0x000001EC0EF51030
generation 1 starts at 0x000001EC0EF51018
generation 2 starts at 0x000001EC0EF51000
ephemeral segment allocation context: none
         segment             begin         allocated         committed    allocated size    committed size
000001EC0EF50000  000001EC0EF51000  000001EC0EF51048  000001EC0EF52000  0x48(72)  0x1000(4096)
Large object heap starts at 0x000001ECFEF51000
         segment             begin         allocated         committed    allocated size    committed size
000001ECFEF50000  000001ECFEF51000  000001ECFEF51018  000001ECFEF52000  0x18(24)  0x1000(4096)
Pinned object heap starts at 0x000001ED7EF51000
000001ED7EF50000  000001ED7EF51000  000001ED7EF51018  000001ED7EF52000  0x18(24)  0x1000(4096)
Allocated Heap Size:       Size: 0x78 (120) bytes.
Committed Heap Size:       Size: 0x2000 (8192) bytes.
------------------------------
GC Allocated Heap Size:    Size: 0x1ab58 (109400) bytes.
GC Committed Heap Size:    Size: 0x30000 (196608) bytes.

總結

 本文主要參閱了微軟官方文件,對GC的工作模式進行了探究和驗證。可以看到,微軟設計這2個GC工作模式的目的是為了.net應用程式在不同場景下有好的程式表現,並沒有孰優孰劣的問題。這個功能特點可以類比現在自動擋汽車的工作模式有節能模式、道路模式、雪地模式、沙漠模式一樣。因此,在實際開發程式時,對普通客戶端應用如winfrom一般選擇workstation模式,對於伺服器程式如asp.net 一般選擇server模式。

參考資料

  1. 垃圾回收的基本知識 | Microsoft Docs
  2. Workstation vs. server garbage collection (GC) | Microsoft Docs
  3. 後臺垃圾回收 | Microsoft Docs
  4. 垃圾回收器配置設定 - .NET | Microsoft Docs

相關文章