記憶體管理/垃圾收集
我們已經看到記憶體管理有多麼複雜,特別是如果你手動分配和釋放記憶體。值得慶幸的是,有一類系統稱為 **垃圾收集器**,可以幫助自動執行記憶體回收過程。
垃圾收集器 (GC) 是用於自動管理動態記憶體的系統或子系統。它們的工作原理如下。
- 與直接呼叫
malloc和free不同,它們被替換為 GC 中名為 "gc_malloc" 的函式。顯然,在實踐中這個函式可以命名為任何東西。 - 當我們在程式中呼叫
gc_malloc時,垃圾收集器會呼叫malloc從系統中分配記憶體,並找到某種方法來跟蹤記憶體。跟蹤記憶體有很多方法,我們將在以後的章節中討論其中的一些方法。 - 程式像往常一樣執行,在需要時從 GC 中分配記憶體,但從不顯式釋放記憶體。
- GC 間歇性地執行一個稱為 **追蹤** 的函式。這可能是一個同步或非同步事件。在追蹤期間,GC 從一組對系統立即可見的記憶體物件開始,稱為記憶體物件的 **根集**。它跟蹤這些記憶體物件中的指標指向子物件。當它到達一個物件時,它會將其標記為 **活動** 的。
- 當 GC 完成追蹤且不再有指標可供跟蹤時,回收階段就會開始。所有未標記為活動的物件都被認為是 **死亡** 的,因為程式中沒有指標指向它們,因此程式不可能訪問它們。GC 會釋放所有死亡物件。
垃圾收集器主要有兩種型別,儘管通常會在這些型別之間採用混合方法來滿足特定需求。第一種型別是可能最直觀的,稱為 **引用計數** 收集器。第二種型別,與我們上面描述的型別最相似,稱為 **追蹤** 收集器。
當 GC 分配一個新的記憶體物件時,它會給該物件一個整數計數字段。每次指向該物件的指標,即引用,計數都會增加。只要計數是一個正非零整數,該物件就處於活動引用狀態並且仍然活動。
當對該物件的引用被移除時,計數會遞減。當計數達到零時,該物件就會死亡,並且可以立即回收。
關於引用計數收集器,有一些要點需要記住。
- 迴圈引用永遠不會被回收,即使整個物件集都死亡了。
- 引用計數是普遍存在的:整個程式必須意識到該系統,並且每個指標引用或取消引用都必須伴隨著適當的增量或遞減。即使在一個大型程式中只維護一次計數失敗,也會為你的程式造成記憶體問題。
- 引用計數可能很昂貴,因為必須對每個指標操作進行計數操作,並且在每次遞減時都必須將計數與零進行比較。這些操作如果使用得足夠頻繁,會為你的程式帶來效能損失。
這些型別的收集器通常被稱為 **協作收集器**,因為它們需要系統其餘部分的協作來維護計數。
追蹤收集器與引用計數收集器完全不同,並且擁有相反的優勢和劣勢。
當追蹤 GC 分配一個新的記憶體塊時,GC 不會建立計數器,但它會建立一個標誌來確定何時標記了該專案,以及一個指向 GC 保留的該物件的指標。這些標誌不是由程式本身操作的,而是在 GC 執行執行時由 GC 操作的。
在 GC 執行期間,程式執行通常會停止。這會導致程式出現間歇性暫停,如果要追蹤的記憶體物件很多,這些暫停可能會非常長。
GC 會選擇一組對當前程式範圍和父範圍可用的根物件。從這些物件開始,GC 會識別物件中所有指標,稱為子物件。物件本身會被標記為活動,然後收集器會移動到每個子物件並以相同的方式標記它們。記憶體物件形成了某種樹形結構,GC 使用遞迴或基於堆疊的方法遍歷這棵樹。
在 GC 執行結束時,當不再有子物件可供標記時,所有未標記的物件都被認為是不可到達的,因此是死亡的。所有死亡物件都會被收集。
關於追蹤 GC,有一些要點需要記住。
- 追蹤 GC 可以用於查詢迴圈,即指標形成迴圈結構的記憶體物件。引用計數方案無法做到這一點。
- 追蹤 GC 會導致程式暫停,這些暫停在某些使用大量小型記憶體物件的複雜程式中可能會變得過長。
- 死亡物件不會立即被回收。回收只會在 GC 執行之後發生。這會導致記憶體使用效率低下。
- 追蹤收集器不需要程式明確地計算記憶體計數或記憶體狀態更新。所有記憶體跟蹤邏輯都儲存在 GC 本身中。這使得為這些系統編寫擴充套件更容易,並且也使得在現有系統中安裝追蹤 GC 比安裝引用計數 GC 更容易。
追蹤 GC 通常被稱為 **非協作收集器**,因為它們不需要系統其餘部分的協作才能正常執行。
有時,引用計數方案會利用追蹤系統來查詢迴圈垃圾。追蹤系統可能會在非常大的物件上使用引用計數,以確保它們能夠快速回收。這僅僅是兩種混合垃圾收集器的例子,它們比上面描述的兩種“純”型別更常見。
在後面的章節中,我們將更詳細地討論垃圾收集器及其演算法。
按任意順序排列,
-
垃圾收集之前的示例。
-
垃圾收集之後的示例。

