# Garbage Collection 垃圾回收機制,從` 保守 `到` 精準 `。
contributed by ` JulianATA `
:::info
從基礎的 Garbage Collector 中的 `mark-and-sweep` 演算法開始,再來探討保守 `Conservative` 垃圾回收的缺點。
延伸 ` mark-and-sweep ` 介紹效能相較優秀的 ` Tri-colored `演算法。了解 ` GO ` 語言中,GC的基礎運作機制。
介紹精準 ` Precise ` 垃圾回收的好處與限制,透過 ` 文獻回顧 ` 與簡介專案 ` magpie ` 了解精準垃圾回收在 `C語言` 實現上的難點。
最後,以 Java8 為基礎,了解 JVM 中進階的垃圾回收機制。
:::
## mark-and-sweep 演算法
### Naive 運作機制:
包含兩個階段 `Mark` 標記和 `Sweep` 掃除。
#### `Mark` Phase
從 root 開始,找尋 `可到達` 的物件。 ( `Reachable Objects` )
標記 `可到達` 的物件。
#### `Sweep` Phase
從 heap 中,找尋未被標記的物件( `Unreachable Objects` ),即需要被清除的物件。
將未被標記的物件加入到 `free list` 中,準備清除。
將已被標記的物件重置為未被標記,以利下次的清掃。
### 如何實作
#### 簡單實作 `Mark`
1. 建立訪問清單 `todo` 將所有 root 中的物件加入。
2. 當 `todo` 中有物件時,執行以下3~4。
3. 從 `todo` 中選任一物件 `v` ,將其除外於 `todo`。
4. 如果 `v` 未被標記,標記 `v` ,將所有 `v` 包含的物件放到 `todo` 中。
#### 簡單實作 `Sweep`
1. 物件 `p` 從 `heap` 的最底層物件開始。
2. 當 `p` 還不是 `top` 的時候,執行以下3~5。
3. 如果 `p` 已被標記,則將其改為未標記。
4. 反之,將 `p` 的記憶體區塊加到 `free list` 中,準備清除。
5. 將 `p` 改為在 heap 中上一層的物件。 ( `using p <- p + sizeof(p)` )
#### 問題點 `1`
* 需要清除時,通常表示沒有記憶體空間。
* 但是, Mark 階段需要記憶體空間去創造訪問清單 。
* 訪問清單無法預測大小,因此無法預先保留。
:::info
解決方法
* 應用 `pointer reversal` ,用 `depth first search` 取代訪問清單。
:::
#### 問題點 `2`
* 有些物件沒有指標的空間。
:::info
解決方法
* 使用一個暫存器記憶其 `parent` 位置,就可以不用 `pointer reversal` 也能執行 `depth firsth search` 。
:::
## Tri-Color 演算法與 Golang
:::info
Reference: [Golang realtime garbage collector](https://www.youtube.com/watch?v=n59VtiRx34s)
:::
上述的基礎 ` mark and sweep ` 演算法有一個缺點。每次在執行 `mark` 的時候,都需要將 process 暫停。
* 在標記的時候,需要讓所有 object 維持不變,才能保持不出錯。
---
* 舉例: 以下 process 有物件 obj0, obj1, obj2, obj3
* 時間t0:執行 ` mark ` 將 ` reachable ` 物件標記。
![](https://i.imgur.com/M8rrw1w.png)
* 時間t1:因為沒有暫停 process,出現 obj4 指向 obj3
![](https://i.imgur.com/8v6mkKy.png)
* 時間t2: 執行 ` sweep `,刪除物件 obj3, obj4 產生錯誤。
![](https://i.imgur.com/jBgwEzS.png)
因此,為了讓 ` mark and sweep ` 過程可以不暫停 process, 延伸出了` Tri-colored `演算法
### Tri-colored 演算法
* 使用 Breadth-first 的策略。
* 將物件用三種顏色來標記:
* 白色:未標記,在 sweep 的階段被清掃
* 灰色:已標記,目前 Breadth-first 的進行前緣。
* 黑色:已標記。
* 將新增物件,標記為灰色。
## Precise Garbage Collector
:::info
Paper Review: [Precise Garbage Collection for C](https://www.cs.utah.edu/plt/publications/ismm09-rwrf.pdf)
:::
### `Precise` GC vs.`Conservative` GC
#### 「保守」 Conservative
在 C 或 C++ 的實作中,`pointer` 的使用沒有硬性規定,因為可能會有 ` unreachable ` 的物件被標為 `reachable` ,原因可能為 type imprecision 或是 liveness imprecision 等等。
* **type imprecision:** 資料型別不明確的情況下,將 ` 非指標 ` 之資料型別作為指標尋找物件。
以下為實作 type imprecision 情況的程式 Based on [ogc](https://github.com/jserv/ogc) .
```clike
#include <assert.h>
#include "gc.h"
void ImPrecise(long *l)
{
int* Unreachable;
Unreachable = gc_alloc(sizeof(int));
assert(Unreachable);
*l = (long)Unreachable;
}
int main(int argc, char *argv[])
{
gc_init(&argc, 1);
long *l;
l = gc_alloc(sizeof(long));
assert(l);
ImPrecise(l);
gc_run();
gc_dump_internals();
gc_destroy();
return 0;
}
```
預計輸出
```bash
## GC internals ##
- GC.ptr_map[20]:
{ ptr: 0x7ffc624006a0, size: 8, marked: 0 }
```
實際輸出
```bash
## GC internals ##
- GC.ptr_map[20]:
{ ptr: 0x7fceecc006a0, size: 8, marked: 0 }
- GC.ptr_map[58]:
{ ptr: 0x7fceecc02bd0, size: 4, marked: 0 }
```
可以看到 `reachable` 的物件,應該只有 type 為 `long` 的 `l` 。
但是 `l` 中儲存的值 `剛好` 等於為一個 `unreachable` 物件的地址。
因此,GC 便保守地將其標為 `reachable` 。
#### 保守 GC 的缺點
> In our experience, however, conservative GC works
poorly for long-running programs, such as a web server, a programming environment, or an operating system kernel.
`Conservative GC` 在小型的程式中有不錯的效果。但在長期執行的 ` long-running ` 程式中,因為沒有持續的記憶體維護對於記憶體管理會造成很大的負擔。
#### PTL Scheme 看 GC
論文中提到對於 [PLT Scheme](https://zh.wikipedia.org/wiki/Racket) ( 現名為Racket ) 的實驗。
* PLT Scheme在當時使用的正是 Conservative GC,每天都會讓記憶體消耗殆盡,導致重新啟動。就算是學生寫的簡單程式,也需要在運行幾小時候重新啟動。
* 替換為 precise GC 後解決了這個問題。每次 Conservative GC 大概回收40MB,反之 Precise GC 可以回收100MB。
*
另外,在 Kernel 中,在使用 linked list 來管理執行緒 ` thread ` 的情況下,可能會造成 unbounded memory 的問題。
其中,很多問題的原因並不只是因為 ` type imprecision ` ,而是因為 ` liveness imprecision ` 。
#### C 語言使用 Precise GC 的限制:
:::info
固然,使用 Precise GC 可以回收更多記憶體,在使用上卻有許多限制。
例1:宣告的型別需要跟 run-time 的行為一樣,至少在「指標與非指標」變數之間,不能混用。
我們常使用 void* 作為 generic pointer type,也會造成他所指向的資料有模糊的狀況。
例2:處理 Union 的使用,也容易造成型態的混用。
:::
因此,讓C語言程式適應 Precise GC 的行為,是一大挑戰。
##### 指標與非指標
> For GC to work, all live objects must be reachable via pointer
chains starting from roots, which include static and local variables with pointer types.
為了讓 GC 可以如期執行,所有`活著`的物件都必須是從 `root` 藉由指標可達的。
為了讓 `precise` GC 能精準的控制資源,所有非指標類的都確保不會被當作 ` GC-allocated ` 物件的指標。
### magpie
## garbage collection in Java
###### tags: ` Cprogramming `