Go:內存管理與內存清理

Go:內存管理與內存清理

Illustration created for “A Journey With Go”, made from the original Go Gopher, created by Renee French.

這篇文章基於 Go 1.13 版本。有關內存管理的討論在我的文章 ” Go:內存管理與分配 ” 中有解釋。

清理內存是一個過程,它能夠讓 Go 知道哪些內存段最近可用於分配。但是,它並不會使用將位置 0 的方式來清理內存。

將內存置 0

將內存置 0 的過程 —— 就是把內存段中的所有位賦值為 0 —— 是在分配過程中即時執行的。

Go:內存管理與內存清理

Zeroing the memory

但是,我們可能想知道 Go 採用什麼樣的策略去知道哪些對象能夠用於分配。由於在每個範圍內有一個內部位圖 allocBits ,Go 實際上會追蹤那些空閒的對象。讓我們從初始態開始來回顧一下它的工作流程,

Go:內存管理與內存清理

Free objects tracking with allocBits

就性能角度來看, allocBits 代表了一個初始態並且會保持不變,但是它會由 freeIndex (一個指向第一個空閒位置的增量計數器)所協助。

然後,第一個分配就開始了:

Go:內存管理與內存清理

Free objects tracking with allocBits

分配過程將會再一次出現,之後, GC 將會啟動去釋放不再被使用的內存。在標記期間,GC 會用一個位圖 gcmarkBits 來跟蹤在使用中的內存。讓我們通過我們運行的程序以相同的示例為例,在第一個塊不再被使用的地方。

Go:內存管理與內存清理

Memory tracking during the garbage collector

Go:內存管理與內存清理

Sweeping a span

但是,這必須在每一個範圍內執行完畢並且會花費許多時間。Go 的目標是在清理內存時不阻礙執行,併為此提供了兩種策略。

清理階段

Go 提供了兩種方式來清理內存:

  • 使用一個工作程序在後臺等待,一個一個的清理這些範圍。
  • 當分配需要一個範圍的時候即時執行。

關於後臺工作程序,當開始運行程序時,Go 將設置一個後臺運行的 Worker(唯一的任務就是去清理內存),它將進入睡眠狀態並等待內存段掃描:

Go:內存管理與內存清理

Background sweeper

通過追蹤過程的週期,我們也能看到這個後臺工作程序總是出現去清理內存:

Go:內存管理與內存清理

Background sweeper

Go:內存管理與內存清理

Spans are released to the central list

Go:內存管理與內存清理

Sweep span on the fly during allocation

即時掃描確保所有內存段在保存資源的過程中都會得到清理,同時會保存資源以及不會阻塞程序執行。

與 GC 週期的衝突

正如之前看到的,由於後臺只有一個 worker 在清理內存塊,清理過程可能會花費一些時間。但是,我們可能想知道如果另一個 GC 週期在一次清理過程中啟動會發生什麼。在這種情況下,這個運行 GC 的 Goroutine 就會在開始標記階段前去協助完成剩餘的清理工作。讓我們舉個例子看一下連續調用兩次 GC,包含數千個對象的內存分配的過程。

Go:內存管理與內存清理

Sweeping must be finished before a new cycle

但是,如果開發者沒有強制調用 GC,這個情況並不會發生。在後臺運行的清理工作以及在執行過程中的清理工作應該足夠多,因為清理內存塊的數量和去觸發一個新的週期(譯者注:GC 週期)的所需的分配的數量成正比。


分享到:


相關文章: