1. 程式人生 > >杭州代孕費用多少

杭州代孕費用多少

杭州代孕費用多少█ 微信 同號█:138-0226-9370██████杭州代孕包成功,代孕包男孩,供卵代孕,三代試管嬰兒選性別,供卵試管嬰兒,十年老品牌代孕公司,

增量式垃圾回收

增量式垃圾回收 Incremental GC

一種通過逐漸推進垃圾回收來控制mutator最大暫停時間的方法。

什麼是增量式垃圾回收

有時候GC時間太長會導致mutator遲遲不能進行。如下圖示:

這樣的GC稱為停止型GC(Stop the world GC)。

為此出現了增量式垃圾回收。增量(incremental)式垃圾回收是將GC和mutator一點點交替執行的手法,如下圖示:

三色標記演算法

描述增量式垃圾回收演算法使用Edsger W.Dijkstra等人提出的三色標記演算法(Tri-color marking)。將GC中的物件按照各自的情況分為三種,使用三種顏色代替。如下

  • 白色:還未搜尋過的物件
  • 灰色:正在搜尋的物件
  • 黑色:搜尋完成的物件

以下使用GC標記-清除演算法為示例:

GC開始執行前的所有物件都是白色,GC一旦開始執行,所有能從根到達的物件都會被標記,然後送到棧裡。這樣的對像是灰色。灰色的物件會從棧中取出,其子物件也會被塗成灰色,當所有子物件全部被塗成灰色,這時該物件就會成為黑色。

GC結束的時候,活動物件全部是黑色,垃圾為白色。

以上僅僅是舉例說明,具體用什麼標記它的顏色,只要能顯示出三種狀態就行。具體使用什麼方法,自己選。

GC 標記清除演算法的分割

GC標記清除演算法那增量式執行的三個階段

  • 根查詢階段:將根直接指向物件標記成灰色
  • 標記階段:將子物件塗成灰色,結束時候所有物件都是黑色
  • 清除階段:查詢清除白色物件連線到空閒連結串列。將黑色物件變為白色物件。

下面是增量式垃圾回收的incremental_gc()函式

incremental_gc(){
    case $gc_phase  // 檢查變數,判斷應該進入那個階段
    when GC_ROOT_SCAN  // 進入查詢階段
        root_scan_phase()
    
    when GC_MARK  // 進入標記階段 incremental_mark_phase() else incremental_sweep_phase() //清除階段 }
  • 當進入根查詢階段,我們直接把根引用的物件打上記號,放入棧中。GC開始時執行一次。
  • 根查詢結束後,incremental_gc()會告一段落,mutator會再次開始執行。
  • 下來再次執行incremental_gc(),函式進入標記階段。在標記階段incremental_mark_phase()函式會從棧中取出物件和搜尋物件。操作一定次數後,mutator會再次開始執行。直到棧標記為空。
  • 之後就是清除階段。incremental_sweep_phase()函式不是一次性清除整個堆,而是每次只清除一定個數,然後中斷GC,再次執行mutator。

根查詢階段

根查詢階段非常簡單。作為根查詢實體的 root_scan_phase() 函式,如程式碼清單所示:

root_scan_phase(){
    for(r : $roots)
        mark(*r)
    $gc_phase = GC_MARK } 

對能直接從根找到的物件呼叫 mark() 函式。mark() 函式的虛擬碼如下所示。

mark(obj){
    if(obj.mark == FALSE) obj.mark = TRUE push(obj, $mark_stack) } 

如果引數 obj 還沒有被標記,那麼就將其標記後堆到標記棧。這個函式正是把 obj 由白色塗成灰色的函式。

當我們把所有直接從根引用的物件塗成了灰色時,根查詢階段就結束了,mutator會繼 續執行。此外,這時 $gc_phase 變成了 GC_MARK。也就是說,下一次 GC 時會進入標記階段。

標記階段

incremental_mark_phase(){
    for(i :1...MARK_MAX)
        if(is_empty($mark_stack) == FALSE) //從棧中取出物件,將其子物件塗成灰色。 obj = pop($mark_stack) for(child :children(obj)) mark(*child) // 遞迴塗子孩子。 else for(r :$roots) // 再次對根直接引用的物件進行標記。因為第一次標記根本沒有進行完,而且之後也可能發生變化。 mark(*r) while(is_empty($mark_stack) == FALSE) obj = pop($mark_stack) for(child :children(obj)) mark(*child) $gc_phase = GC_SWEEP // 為清除階段做準備 $sweeping = $heap_start return }
  • 可以看到首先從棧中取出物件,將其子物件塗成灰色。但是這一系列操作只執行了MARK_MAX次。我們知道增量式的垃圾回收不是一次性處理完了。所以這個MARK_MAX就顯得格外重要了
  • 之後在標記即將結束前,對根物件指向的物件再次標記。原因如下圖示:

我們可以看到由於增量式垃圾回收它是一步一步走的,並不是說一次就把GC做完,所以它在GC的過程中指標時會變化的。如果變化如上圖,我們又不對其重新標記,那得到的結果就是,C物件被刪掉了。很嚴重的一個後果啊。

為了防止這樣,我們又一次的使用了寫入屏障。

寫入屏障

看一下Edsger W. Dijkstra 等人提出的寫入屏障

write_barrier(obj, field, newobj){
    if(newobj.mark == FALSE) newobj.mark = TRUE push(newobj, $mark_stack) *field = newobj }

如果新引用的物件newobj沒有被標記過,就將其標記後堆到標記棧裡。

即使在 mutator 更新指標後的圖中c,也沒有產生從黑色物件指向白色物件的引用。這樣一來我們就成功地防止了標記遺漏。

清除階段

當標記棧為空時,GC就會進入清除階段。程式碼清單如下:

incremental_sweep_phase(){
    swept_count = 0
    while(swept_count < SWEEP_MAX)
        if($sweeping < $heap_end)
            if($sweeping.mark ==TRUE) $sweeping.mark = FALSE else $sweeping.next = $free_list $free_list = $sweeping $free_size += $sweeping.size $sweeping += $sweeping.size swept_count++ else $gc_phase = GC_ROOT_SCAN return }

該函式所進行的操作就是把沒被標記的物件連線到空閒連結串列,取消已標記的 物件的標誌位。

為了只對一定數量的物件進行回收,事先準備swept_count用來記錄數量。swept_count >= SWEEP_MAX 時,就暫停清除階段,再次執行 mutator。當把堆全部清除完畢時,就將 $gc_phase 設為 GC_ROOT_SCAN,結束 GC

分配

newobj(size){
    if($free_size < HEAP_SIZE * GC_THRESHOLD) // 如果分塊的總量 $free_size 少於一定的量HEAP_SIZE就執行GC
        incremental_gc()
    
    chunk = pickup_chunk(size, $free_list) // 搜尋空閒連結串列返回大小時size的塊
    if(chunk != NULL) chunk.size = size $free_size -= size if($gc_phase == GC_SWEEP && $sweeping <= chunk) // 判斷GC是否在清除階段和chunk是不是在已清除完畢的空間 chunk.mark = TRUE // 沒有在清除完畢的空間,我們要設定標誌位 return chunk else allocation_fail() }
  • 判斷$free_size 是不是小於HEAP_SIZE * GC_THRESHOLD,如果是就執行GC。
  • 在空閒連結串列查詢大小為size的分塊,並返回。
  • 對分塊進行標記,對$free_size進行後移操作。
  • 判斷 GC狀態,和chunk狀態。
  • 如果chunk在清除完畢的空間的空間裡什麼都不做,如果不在則進行標記。

優點和缺點

縮短最大暫停時間

  • 增量式垃圾回收通過交替執行GC和mutator來減少停止時間,減少二者的相互影響。從而保證GC不會長時間妨礙mutator。
  • 增量式垃圾回收不是重視吞吐量,而是重視如何縮短最大暫停時間。

降低了吞吐量

  • 寫入屏障會增加額外負擔。但是這是必要的犧牲啊啊啊。
  • 高吞吐量和縮短最大暫停時間,二者不可兼得。根據需要選擇最合適的最好。

Steele 的演算法

1975,Guy.Steele

這個演算法中使用的寫入屏障條件更嚴格,它能減少GC中錯誤標記的物件。

mark()函式

mark(obj){
    if(obj.mark == FALSE)
        push(obj, $mark_stack)
}

再把物件放入標記棧的時候還沒有標記,在這個演算法中從標記棧取出時才為它設定標記為。這裡的灰色物件時“標記棧裡的沒有設定標記位置的物件”,黑色是設定了標識位的物件。

寫入屏障

write_barrier(obj, fieldm newobj){
    if($gc_phase == GC_MARK && obj.mark == TRUE && newobj.mark == FALSE) obj.mark = FALSE push(obj, $mark_stack) *field = newobj } 
  • 判斷條件,條件成立時,將obj.mark設定為FALSE
  • 將obj 放入標記棧。
  • 如果標記過程中發出引用的物件時黑色,且新的引用物件為灰色或者白色,那麼我們就把發出引用的物件塗成灰色。

如上圖示:寫入屏障在a到b中發揮了作用。物件A被塗成了灰色,其結果就是c中不存在從黑色物件指向的白色物件,也就不會出現把活動物件標記遺漏的狀況了。

當A物件為灰色的時候,我們會再次對A物件進行搜尋和標記。

湯淺的演算法(不放入cnblog)

湯淺太一,1990 Snapshot GC

這種演算法是以GC開始時物件間的引用關係(snapshot)為基礎來執行GC的。因此,根據湯淺演算法,在GC開始時回收垃圾,保留GC開始時的活動物件和GC執行過程中被分配的物件。

標記階段

incremental_mark_phase(){
    for(i :1..MARK_MAX)
        if(is_empty($mark_stack) == FALSE)
            obj = pop($mark_stack)
            for(child: children(obj)) mark(*child) else $gc_phase = GC_SWEEP $sweeping = $heap_start return }
  • 在湯淺演算法中,清除階段沒有必要再去搜索根了,因為該演算法以GC開始時物件間的引用關係為基礎執行GC。
  • 在標記階段中,新的從根引用的物件在GC開始時應該會被別的物件鎖引用。因此搜尋GC開始時就存在的指標,就會發現這個物件已經被標記完畢了。所以沒有必要從新的根重新標記它。

從黑色物件指向白色物件的指標

之前我們提到過,使用寫入屏障來防止產生從黑色物件指向白色物件的指標。但是湯淺演算法中我們允許黑色物件指向白色物件。這樣還能回收成功的原因是因為GC一開始就保留活動物件的這項原則。

遵循這項原則,就沒有必要在新生成指標時標記引用的目標子物件。即使生成了從黑色物件指向白色物件的指標,只要保留了GC開始時的指標,作為引用目標的白色物件早晚都會被標記。

其實指標被刪除時的情況應該引起我們的注意。指向物件的指標刪除,就可能無法保留GC開始時的活動物件了。因此在湯淺的寫入屏障中,再刪除指向物件的指標時要進行特殊處理。

寫入屏障

write_barrier(obj, field, newobj){
    oldobj = *field if(gc_phase == GC_MARK && oldobj.mark == FALSE) oldobj.mark = TRUE push(oldobj, $mark_stack) *field = newobj }

當GC進入到標記階段且oldobj是白色物件,就將其塗成灰色。

圖b轉移到圖c的過程中寫入屏障發揮了作用,他把c塗成了灰色,這樣就防止c的標記遺漏。

圖b中,黑色物件指向了白色物件。但是B指向C並沒有被刪除。在湯淺的寫入屏障中這時候不會進行特殊的處理。只有當B指向C的指標被刪除的時候,C才會變為灰色。

分配

newobj(size){
    if($free_size < HEAP_SIZE * GC_THRESHOLD)
        incremental_gc()
    
    chunk = pickup_chunk(size, $free_list)
    if(chunk != NULL)
        chunk.size = size
        $free_size -= size
        if($gc_phase == GC_MARK)
            chunk.mark = TRUE else if($gc_phase == GC_SWEEP && $sweeping <= chunk) chunk.mark = TRUE return chunk else allocation_fail() }

在標記階段進行分配時會無條件設定obj的標誌位。也就是說,會把obj塗成黑色。湯淺演算法的寫入屏障比較簡單,所以保留了很多物件,無意間也保留了很多垃圾物件。

比較各個寫入屏障

作者 A B C 時機 動作
Dijkstra     從a到b 將C塗成灰色
Steele   白或灰色 從a到b 將A恢復為灰色
湯淺     從b到c 將C塗成灰色
  分類:  垃圾回收演算法

增量式垃圾回收

增量式垃圾回收 Incremental GC

一種通過逐漸推進垃圾回收來控制mutator最大暫停時間的方法。

什麼是增量式垃圾回收

有時候GC時間太長會導致mutator遲遲不能進行。如下圖示:

這樣的GC稱為停止型GC(Stop the world GC)。

為此出現了增量式垃圾回收。增量(incremental)式垃圾回收是將GC和mutator一點點交替執行的手法,如下圖示:

三色標記演算法

描述增量式垃圾回收演算法使用Edsger W.Dijkstra等人提出的三色標記演算法(Tri-color marking)。將GC中的物件按照各自的情況分為三種,使用三種顏色代替。如下

  • 白色:還未搜尋過的物件
  • 灰色:正在搜尋的物件
  • 黑色:搜尋完成的物件

以下使用GC標記-清除演算法為示例:

GC開始執行前的所有物件都是白色,GC一旦開始執行,所有能從根到達的物件都會被標記,然後送到棧裡。這樣的對像是灰色。灰色的物件會從棧中取出,其子物件也會被塗成灰色,當所有子物件全部被塗成灰色,這時該物件就會成為黑色。

GC結束的時候,活動物件全部是黑色,垃圾為白色。

以上僅僅是舉例說明,具體用什麼標記它的顏色,只要能顯示出三種狀態就行。具體使用什麼方法,自己選。

GC 標記清除演算法的分割

GC標記清除演算法那增量式執行的三個階段

  • 根查詢階段:將根直接指向物件標記成灰色
  • 標記階段:將子物件塗成灰色,結束時候所有物件都是黑色
  • 清除階段:查詢清除白色物件連線到空閒連結串列。將黑色物件變為白色物件。

下面是增量式垃圾回收的incremental_gc()函式

incremental_gc(){
    case $gc_phase  // 檢查變數,判斷應該進入那個階段
    when GC_ROOT_SCAN  // 進入查詢階段
        root_scan_phase()
    
    when GC_MARK  // 進入標記階段 incremental_mark_phase() else incremental_sweep_phase() //清除階段 }
  • 當進入根查詢階段,我們直接把根引用的物件打上記號,放入棧中。GC開始時執行一次。
  • 根查詢結束後,incremental_gc()會告一段落,mutator會再次開始執行。
  • 下來再次執行incremental_gc(),函式進入標記階段。在標記階段incremental_mark_phase()函式會從棧中取出物件和搜尋物件。操作一定次數後,mutator會再次開始執行。直到棧標記為空。
  • 之後就是清除階段。incremental_sweep_phase()函式不是一次性清除整個堆,而是每次只清除一定個數,然後中斷GC,再次執行mutator。

根查詢階段

根查詢階段非常簡單。作為根查詢實體的 root_scan_phase() 函式,如程式碼清單所示:

root_scan_phase(){
    for(r : $roots)
        mark(*r)
    $gc_phase = GC_MARK } 

對能直接從根找到的物件呼叫 mark() 函式。mark() 函式的虛擬碼如下所示。

mark(obj){
    if(obj.mark == FALSE) obj.mark = TRUE push(obj, $mark_stack) } 

如果引數 obj 還沒有被標記,那麼就將其標記後堆到標記棧。這個函式正是把 obj 由白色塗成灰色的函式。

當我們把所有直接從根引用的物件塗成了灰色時,根查詢階段就結束了,mutator會繼 續執行。此外,這時 $gc_phase 變成了 GC_MARK。也就是說,下一次 GC 時會進入標記階段。

標記階段

incremental_mark_phase(){
    for(i :1...MARK_MAX)
        if(is_empty($mark_stack) == FALSE) //從棧中取出物件,將其子物件塗成灰色。 obj = pop($mark_stack) for(child :children(obj)) mark(*child) // 遞迴塗子孩子。 else for(r :$roots) // 再次對根直接引用的物件進行標記。因為第一次標記根本沒有進行完,而且之後也可能發生變化。 mark(*r) while(is_empty($mark_stack) == FALSE) obj = pop($mark_stack) for(child :children(obj)) mark(*child) $gc_phase = GC_SWEEP // 為清除階段做準備 $sweeping = $heap_start return }
  • 可以看到首先從棧中取出物件,將其子物件塗成灰色。但是這一系列操作只執行了MARK_MAX次。我們知道增量式的垃圾回收不是一次性處理完了。所以這個MARK_MAX就顯得格外重要了
  • 之後在標記即將結束前,對根物件指向的物件再次標記。原因如下圖示:

我們可以看到由於增量式垃圾回收它是一步一步走的,並不是說一次就把GC做完,所以它在GC的過程中指標時會變化的。如果變化如上圖,我們又不對其重新標記,那得到的結果就是,C物件被刪掉了。很嚴重的一個後果啊。

為了防止這樣,我們又一次的使用了寫入屏障。

寫入屏障

看一下Edsger W. Dijkstra 等人提出的寫入屏障

write_barrier(obj, field, newobj){
    if(newobj.mark == FALSE) newobj.mark = TRUE push(newobj, $mark_stack) *field = newobj }

如果新引用的物件newobj沒有被標記過,就將其標記後堆到標記棧裡。

即使在 mutator 更新指標後的圖中c,也沒有產生從黑色物件指向白色物件的引用。這樣一來我們就成功地防止了標記遺漏。

清除階段

當標記棧為空時,GC就會進入清除階段。程式碼清單如下:

incremental_sweep_phase(){
    swept_count = 0
    while(swept_count < SWEEP_MAX)
        if($sweeping < $heap_end)
            if($sweeping.mark ==TRUE) $sweeping.mark = FALSE else $sweeping.next = $free_list $free_list = $sweeping $free_size += $sweeping.size $sweeping += $sweeping.size swept_count++ else $gc_phase = GC_ROOT_SCAN return }

該函式所進行的操作就是把沒被標記的物件連線到空閒連結串列,取消已標記的 物件的標誌位。

為了只對一定數量的物件進行回收,事先準備swept_count用來記錄數量。swept_count >= SWEEP_MAX 時,就暫停清除階段,再次執行 mutator。當把堆全部清除完畢時,就將 $gc_phase 設為 GC_ROOT_SCAN,結束 GC

分配

newobj(size){
    if($free_size < HEAP_SIZE * GC_THRESHOLD) // 如果分塊的總量 $free_size 少於一定的量HEAP_SIZE就執行GC
        incremental_gc()
    
    chunk = pickup_chunk(size, $free_list) // 搜尋空閒連結串列返回大小時size的塊
    if(chunk != NULL) chunk.size = size $free_size -= size if($gc_phase == GC_SWEEP && $sweeping <= chunk) // 判斷GC是否在清除階段和chunk是不是在已清除完畢的空間 chunk.mark = TRUE // 沒有在清除完畢的空間,我們要設定標誌位 return chunk else allocation_fail() }
  • 判斷$free_size 是不是小於HEAP_SIZE * GC_THRESHOLD,如果是就執行GC。
  • 在空閒連結串列查詢大小為size的分塊,並返回。
  • 對分塊進行標記,對$free_size進行後移操作。
  • 判斷 GC狀態,和chunk狀態。
  • 如果chunk在清除完畢的空間的空間裡什麼都不做,如果不在則進行標記。

優點和缺點

縮短最大暫停時間

  • 增量式垃圾回收通過交替執行GC和mutator來減少停止時間,減少二者的相互影響。從而保證GC不會長時間妨礙mutator。
  • 增量式垃圾回收不是重視吞吐量,而是重視如何縮短最大暫停時間。

降低了吞吐量

  • 寫入屏障會增加額外負擔。但是這是必要的犧牲啊啊啊。
  • 高吞吐量和縮短最大暫停時間,二者不可兼得。根據需要選擇最合適的最好。

Steele 的演算法

1975,Guy.Steele

這個演算法中使用的寫入屏障條件更嚴格,它能減少GC中錯誤標記的物件。

mark()函式

mark(obj){
    if(obj.mark == FALSE)
        push(obj, $mark_stack)
}

再把物件放入標記棧的時候還沒有標記,在這個演算法中從標記棧取出時才為它設定標記為。這裡的灰色物件時“標記棧裡的沒有設定標記位置的物件”,黑色是設定了標識位的物件。

寫入屏障

write_barrier(obj, fieldm newobj){
    if($gc_phase == GC_MARK && obj.mark == TRUE && newobj.mark == FALSE) obj.mark = FALSE push(obj, $mark_stack) *field = newobj } 
  • 判斷條件,條件成立時,將obj.mark設定為FALSE
  • 將obj 放入標記棧。
  • 如果標記過程中發出引用的物件時黑色,且新的引用物件為灰色或者白色,那麼我們就把發出引用的物件塗成灰色。

如上圖示:寫入屏障在a到b中發揮了作用。物件A被塗成了灰色,其結果就是c中不存在從黑色物件指向的白色物件,也就不會出現把活動物件標記遺漏的狀況了。

當A物件為灰色的時候,我們會再次對A物件進行搜尋和標記。

湯淺的演算法(不放入cnblog)

湯淺太一,1990 Snapshot GC

這種演算法是以GC開始時物件間的引用關係(snapshot)為基礎來執行GC的。因此,根據湯淺演算法,在GC開始時回收垃圾,保留GC開始時的活動物件和GC執行過程中被分配的物件。

標記階段

incremental_mark_phase(){
    for(i :1..MARK_MAX)
        if(is_empty($mark_stack) == FALSE)
            obj = pop($mark_stack)
            for(child: children(obj)) mark(*child) else $gc_phase = GC_SWEEP $sweeping = $heap_start return }
  • 在湯淺演算法中,清除階段沒有必要再去搜索根了,因為該演算法以GC開始時物件間的引用關係為基礎執行GC。
  • 在標記階段中,新的從根引用的物件在GC開始時應該會被別的物件鎖引用。因此搜尋GC開始時就存在的指標,就會發現這個物件已經被標記完畢了。所以沒有必要從新的根重新標記它。

從黑色物件指向白色物件的指標

之前我們提到過,使用寫入屏障來防止產生從黑色物件指向白色物件的指標。但是湯淺演算法中我們允許黑色物件指向白色物件。這樣還能回收成功的原因是因為GC一開始就保留活動物件的這項原則。

遵循這項原則,就沒有必要在新生成指標時標記引用的目標子物件。即使生成了從黑色物件指向白色物件的指標,只要保留了GC開始時的指標,作為引用目標的白色物件早晚都會被標記。

其實指標被刪除時的情況應該引起我們的注意。指向物件的指標刪除,就可能無法保留GC開始時的活動物件了。因此在湯淺的寫入屏障中,再刪除指向物件的指標時要進行特殊處理。

寫入屏障

write_barrier(obj, field, newobj){
    oldobj = *field if(gc_phase == GC_MARK && oldobj.mark == FALSE) oldobj.mark = TRUE push(oldobj, $mark_stack) *field = newobj }

當GC進入到標記階段且oldobj是白色物件,就將其塗成灰色。

圖b轉移到圖c的過程中寫入屏障發揮了作用,他把c塗成了灰色,這樣就防止c的標記遺漏。

圖b中,黑色物件指向了白色物件。但是B指向C並沒有被刪除。在湯淺的寫入屏障中這時候不會進行特殊的處理。只有當B指向C的指標被刪除的時候,C才會變為灰色。

分配

newobj(size){
    if($free_size < HEAP_SIZE * GC_THRESHOLD)
        incremental_gc()
    
    chunk = pickup_chunk(size, $free_list)
    if(chunk != NULL)
        chunk.size = size
        $free_size -= size
        if($gc_phase == GC_MARK)
            chunk.mark = TRUE else if($gc_phase == GC_SWEEP && $sweeping <= chunk) chunk.mark = TRUE return chunk else allocation_fail() }

在標記階段進行分配時會無條件設定obj的標誌位。也就是說,會把obj塗成黑色。湯淺演算法的寫入屏障比較簡單,所以保留了很多物件,無意間也保留了很多垃圾物件。

比較各個寫入屏障

作者 A B C 時機 動作
Dijkstra     從a到b 將C塗成灰色
Steele   白或灰色 從a到b 將A恢復為灰色
湯淺     從b到c 將C塗成灰色