垃圾回收算法(3)複製

垃圾回收複製算法的基本思想很直觀,甚至看上去浪費。將現有堆一分爲二,一個用完後,將活動對象拷貝到另外一半。可是這樣作卻有一些比較明顯的優勢。
 
先來看下具體算法。
 
看上去簡單,其實仍是有一些要注意的地方。
第一是防止重複拷貝,對象之間的引用能夠很複雜,各類交叉;第二是拷貝後,新老堆中的引用關係要縷順。
 
copying() {
  $free = $to_start
  for (r : $root) 
    *r = copy(r)  // 注意,這裏實際是一個update,將對象替換
 
  swap($from_start, $to_start)
}

 

上述free是新堆的起點指針,複製回收剛開始時,free指向起點。開頭描述的兩個要注意的地方能夠經過在老堆中的對象中引入兩個字段來解決,分別是tag、forwarding,由於老堆中內存已經無用,兩字段能夠在對象原先的字段中重用。具體見下算法:
 
// 這裏,傳給copy的對象,必定是舊堆的
copy(obj) {
  if obj.tag != COPIED                       // 重要,在老堆的對象中,標誌這對象已經拷走。
    copy_data($free, obj, obj.size)       // 拷貝到free
    obj.tag = COPIED
    obj.forwarding = $free                  // 重要!obj是老堆中對象,forwarding字段指向了新堆中的同一對象!!
    $free += obj.size
 
  for (child : children(obj.forwarding) 
    child = copy(child)                 //重要,又是一個update操做,不單單是遞歸處理,也是遞歸賦值。將老的
 
  return obj.forwarding             // 最終返回
}
 

與此相對,分配變得更簡單:算法

new_obj(size) {
  if ($free + size > $from_start + HEAD_SIZE/2)
    copying()                                              // 超過一半觸發
    if ($free + size > $from_start + HEAP_SIZE/2)
      fail()
 
  obj = $free                         // 分配直接從指針處取內存!
  obj.size = size
  $free += size
  return obj
}

以上即是複製算法,可見不復雜。緩存

 
優勢:
  1. 吞吐量高,由於並無像mark_sweep那種全堆掃描處理,只處理活動對象;
  2. 不使用free_list,分配內存階段高效;
  3. 對象所有緊鄰,沒有碎片;
  4. 有引用關係的對象內存相鄰,緩存利用率好。
 
缺點:
  1. 堆二等分。。。。。
  2. 必須移動對象、改寫指針
  3. 遞歸複製,容易形成棧溢出
 
 
針對以上缺點,來看一下改進項:
 
-》 上述第3點,遞歸複製,實際上是個深度優先的遍歷。針對這種順序型帶指針引用的內存結構,能夠優化成一個巧妙的廣度優先。廣度優先須要一個隊列,這裏能夠人爲設置兩個下標,下標之間的空間做爲隊列。
以下:
 
copying() {
  scan = $free = $to_start        // scan是一個邏輯隊列的頭,free則是這個邏輯隊列的尾
  for (r : $root)
    r = copy(r)                       // 從root出發,先走一層。拉開scan與free的差距,理解成邏輯上將root的第一層子節點入隊列。具體要看後面copy實現
 
  while (scan != free)
    for (child : children(scan))
      child = copy(child)
    scan += scan.size
 
  swap($from_start, $to_start)
}
 
copy(obj) {
  if !(obj.forwarding belong $to_start)     // 此處能夠直接拿forwarding字段來判斷是否已經拷貝到新堆空間
    copy_data($free, obj, obj.size)
    obj.forwarding = $free                     // 老對象指向新對象
    $free += obj.size
  return obj.forwarding
}

 

上述算法雖然巧妙,但廣度優先的方式會破壞深度優先的緩存利用率高的優勢。
對這個方法繼續優化、細化:
 
考慮到內存的加載是以page爲單位,那麼對於內存堆的複製也能夠以page爲單位。
在page內部,使用深度優先的算法,而跨page時,可使用廣度優先方式避免過分遞歸拷貝。
能夠理解成由一維的堆引伸出一個二維的堆。其中一維使用深度優先保證內存讀取緩存的命中;另外一維度是在page間使用廣度優先防止過深的遞歸拷貝帶來問題。具體是使每一個page內維護一個local_scan下標,這裏不展開。
 
-》 上述第1點,堆利用率,這是個最顯著的缺點了
能夠採用一些折衷的算法。好比,將堆分紅N份,其中的二份之間採用複製垃圾回收。其餘的採用mark_sweep。
相關文章
相關標籤/搜索