.NET GC工作流程

2022-07-11 12:01:57

前言

在上文[如何獲取GC的STW時間]一文中,我們聊到了如何通過監聽GC發出的診斷事件來計算STW時間。裡面只簡單的介紹了幾種GC事件和它的流程。
群裡就有小夥伴在問,那麼GC事件是什麼時候產生的?分別是代表什麼含義?
那麼在本文就通過幾個圖為大家解答一下這個問題。

有哪些GC模式?

工作站和伺服器模式

在.NET中,GC其實有一些不同的工作模式,根據使用者端和伺服器可以分為如下兩種模式:

Workstation GC

Workstation GC(工作站GC),這種模式主要是為了滿足基於UI的互動式應用程式設計的,互動式意味著GC的暫停時間要儘可能的短。因為我們不想因為觸發GC導致較長的GC停頓。

  • GC會更頻繁的發生,每次暫停時間都會很短。
  • 記憶體佔用率更低,因為GC更頻繁的發生,所以記憶體回收的更積極,佔用率也會更低。
  • 無論是否有設定多CPU核心,垃圾回收始終只使用一個CPU核心,只有一個託管堆。
  • 記憶體段的大小設定會很小。
Server GC

Server GC (伺服器GC),這種模式主要是為了滿足基於請求處理的WEB等型別應用程式設計的,這意味著它更側重於需要滿足大的吞吐量,零星的停頓不會對齊產生重大的影響。

  • GC的發生頻率會降低,優先滿足大吞吐量。
  • 記憶體佔用率會更高,因為GC發生的頻率變低,記憶體中可能會有很多垃圾物件。
  • 垃圾回收使用高優先順序執行在多個專用執行緒上。每個CPU核心都提供執行垃圾回收的專用執行緒和堆,每個CPU核心上的堆都包含小物件、大物件堆。
  • 因為多個垃圾回收執行緒一起工作,所以對於相同大小的堆,Server GC會回收的更快一些。
  • 伺服器垃圾回收通常會有更大的Segment,另外也會佔用更多的資源。

並行與非並行模式

另外根據GC相對於使用者執行緒的操作方式,還可以分為下面兩種方式:

Non-Concurrent

Non-Concurrent(非並行GC),這種方式是一直存在於.NET中的,它適用於工作站和伺服器模式,在GC進行過程中,所有的使用者執行緒都會掛起

Concurrent(已過時)

Concurrent (並行GC),並行GC模式它和使用者執行緒同時工作,GC進行過程中只有少數幾個過程需要掛起使用者執行緒。所以它的實現也更加複雜,但是暫停時間會更短,效能也會更好,不過現在它已經過時,本文不會著重描述它。

Background

Background(後臺GC),在.NET Framework 4.0以後,後臺GC取代了並行GC,它只適用於Gen2的回收,但是它可以觸發對於Gen0、Gen1的回收。根據WorkstationGC和ServerGC的模式會分別在一個或多個執行緒上執行。

GC工作流程

需要知道的GC事件

其實對於我們分析GC的工作來說,上文中提到的幾個事件已經足夠使用了,讓我們再來回顧一下這些事件。

Microsoft-Windows-DotNETRuntime/GC/SuspendEEStart	//開始暫停託管執行緒執行
Microsoft-Windows-DotNETRuntime/GC/SuspendEEStop	//暫停託管執行緒完成
Microsoft-Windows-DotNETRuntime/GC/Start	// GC開始回收
Microsoft-Windows-DotNETRuntime/GC/Stop		// GC回收結束
Microsoft-Windows-DotNETRuntime/GC/RestartEEStart	//恢復之前暫停的託管執行緒
Microsoft-Windows-DotNETRuntime/GC/RestartEEStop	//恢復託管執行緒執行完成

圖例

為了讓大家能更清晰的看懂下面的圖,會用不同形狀和顏色的影象來代表不同的含義,如下方所示:

綠色:正在執行的使用者執行緒。
紅色:執行引擎進行執行緒凍結或執行緒恢復。
實線箭頭:正在執行的GC執行緒。
虛線箭頭:被暫停的執行緒。
黃色圓球:GC事件。
紅色圓球:標記點。

WorkstationGC模式-非後臺(並行)GC

下圖是WorkStationGC(非後臺)模式的執行流程,我們假設它是在一個雙核的機器上執行(下文中都是假設在雙核機器上執行),執行過程其實就像下圖所示。

在上圖中的事件流如下所示:

  1. GC/SuspendEEStart
  2. GC/SuspendEEStop
  3. GC/Start
  4. GC/Stop
  5. GC/RestartEEStart
  6. GC/RestartEEStop

其中各個標記點分別完成了如下工作:

  • A->B:暫停所有使用者執行緒
  • B->C: 挑選一個使用者執行緒作為GC執行緒,然後開始進行垃圾回收
    • 選擇-需要被回收的一代
    • 標記-被回收的一代和更年輕一代物件
    • 計劃-GC決定是需要壓縮整理堆還是隻是清掃堆就夠了
    • 清掃、搬遷和壓縮-根據上面計劃的結果,執行清掃堆,或者搬遷活著的物件然後整理堆,最後所有物件的地址更新到新地址。
  • C->D: GC工作結束,恢復執行緒執行
    由於GC暫停了所有的執行緒,所以A->D就是此類GC的STW Time時間。

ServerGC模式-非後臺(並行)GC

下圖是ServerGC(非後臺)模式的執行流程。

它與WorkstationGC模式的事件流和完成的工作都一致,唯一不同的就是它會根據當前的CPU邏輯核心數量建立單獨的GC執行緒,比如上圖就有2個GC執行緒。
另外在伺服器GC模式中,使用者執行緒還是可以作為GC執行緒來使用的,像使用者執行緒1在GC發生的時候就做了一些GC工作。

WorkstationGC模式-後臺GC

下圖是WorkstationGC(後臺)模式的執行流程,可以看到後臺模式還是相當複雜的,會短暫的暫停多次,每一次都會執行不同的操作。

除了工作執行緒GC以外,另外會有單獨的後臺GC執行緒進行後臺垃圾回收。
上圖中的事件流如下所示:

  1. GC/SuspendEEStart
  2. GC/SuspendEEStop
  3. GC/Start
  4. GC/RestartEEStart
  5. GC/RestartEEStop
  6. GC/SuspendEEStart
  7. GC/SuspendEEStop
  8. GC/RestartEEStart
  9. GC/RestartEEStop
  10. GC/SuspendEEStart
  11. GC/SuspendEEStop
  12. GC/Start
  13. GC/Stop
  14. GC/RestartEEStart
  15. GC/RestartEEStop
  16. GC/Stop

其中各個標記點完成的工作如下所示:

  • A->B:初始選擇、標記
    • 此時使用者執行緒是暫停的
    • 選擇需要被回收的一代
    • 找到GC roots,以便並行標記
  • B->C:並行標記
    • 此時使用者執行緒是正常執行的
    • 從上一步中找到的GC roots開始標記需要被回收的一代和年輕的代
  • D->E:最終標記
    • 此時使用者執行緒是暫停的
    • 掃描在並行標記過頁面,看看是否有修改讓物件重新活過來的
  • F->G:清掃小物件堆
    • 此時使用者執行緒是正常執行的
    • 清掃小物件堆的物件
  • H->I:壓縮整理小物件堆、清掃壓縮整理大物件堆
    • 此時使用者執行緒是暫停的
    • 選擇了一個使用者執行緒進行GC
    • 用來壓縮小物件堆的物件
    • 另外也會壓縮和整理大物件堆物件
  • J->K:清掃大物件堆
    • 此時使用者執行緒是正常執行的
    • 此時會清掃和整理大的物件堆
    • 此時會禁止分配大物件,阻塞對應執行緒直到大物件堆回收完成

從上面的的流程中可以看到,後臺GC主要是通過並行+多次短暫暫停來實現提升吞吐量和降低總體的STW Time的,其內部實現是非常複雜的,有興趣的小夥伴可以直接看dotnet/runtime/gc.cpp檔案。

ServerGC模式-非後臺GC

下圖是ServerGC(後臺)模式的執行流程。

它與WorkstationGC模式的事件流和完成的工作都一致,唯一不同的就是它會根據當前的CPU邏輯核心數量建立單獨的GC執行緒,比如上圖就有2個GC執行緒,2個後臺GC執行緒。

總結

今天帶了解了一下.NET GC中的各個階段和事件的順序,當然這裡只是簡單的帶大家瞭解一下,要知道在任何有runtime的平臺中,GC是其中相當關鍵的東西,大家如果對GC感興趣,可以閱讀附錄中的資料。

附錄