1. 程式人生 > >JVM 新生代為何需要兩個 Survivor 空間?

JVM 新生代為何需要兩個 Survivor 空間?

轉載http://dsxwjhf.iteye.com/blog/2201687

我們知道,目前主流的虛擬機器實現都採用了分代收集的思想,把整個堆區劃分為新生代和老年代;新生代又被劃分成 Eden 空間、 From Survivor 和 To Survivor 三塊區域。 

看書的時候有個疑問,為什麼非得是兩個 Survivor 空間呢?要回答這個問題,其實等價於:為什麼不是0個或1個 Survivor 空間?為什麼2個 Survivor 空間可以達到要求? 

為什麼不是0個 Survivor 空間?

這個問題等價於:為什麼需要 Survivor 空間。我們看看如果沒有 Survivor 空間的話,垃圾收集將會怎樣進行:一遍新生代 gc 過後,不管三七二十一,活著的物件全部進入老年代,即便它在接下來的幾次 gc 過程中極有可能被回收掉。這樣的話老年代很快被填滿, Full GC 的頻率大大增加。我們知道,老年代一般都會被規劃成比新生代大很多,對它進行垃圾收集會消耗比較長的時間;如果收集的頻率又很快的話,那就更糟糕了。基於這種考慮,虛擬機器引進了“倖存區”的概念:如果物件在某次新生代 gc 之後任然存活,讓它暫時進入倖存區;以後每熬過一次 gc ,讓物件的年齡+1,直到其年齡達到某個設定的值(比如15歲), JVM 認為它很有可能是個“老不死的”物件,再呆在倖存區沒有必要(而且老是在兩個倖存區之間反覆地複製也需要消耗資源),才會把它轉移到老年代。 



總之,設定 Survivor 空間的目的是讓那些中等壽命的物件儘量在 Minor GC 時被幹掉,最終在總體上減少虛擬機器的垃圾收集過程對使用者程式的影響。 

為什麼不是1個 Survivor 空間?

回答這個問題有一個前提,就是新生代一般都採用複製演算法進行垃圾收集。原始的複製演算法是把一塊記憶體一分為二, gc 時把存活的物件從一塊空間(From space)複製到另外一塊空間(To space),再把原先的那塊記憶體(From space)清理乾淨,最後調換 From space 和 To space 的邏輯角色(這樣下一次 gc 的時候還可以按這樣的方式進行)。 

我們知道,在 HotSpot 虛擬機器裡, Eden 空間和 Survivor 空間預設的比例是 8:1 。我們來看看在只有一個 Survivor 空間的情況下,這個 8:1 會有什麼問題。此處為了方便說明,我們假設新生代一共為 9 MB 。物件優先在 Eden 區分配,當 Eden 空間滿 8 MB 時,觸發第一次 Minor GC 。比如說有 0.5 MB 的物件存活,那這 0.5 MB 的物件將由 Eden 區向 Survivor 區複製。這次 Minor GC 過後, Eden 區被清理乾淨, Survivor 區被佔用了 0.5 MB ,還剩 0.5 MB 。到這裡一切都很美好,但問題馬上就來了:從現在開始所有物件將會在這剩下的 0.5 MB 的空間上被分配,很快就會發現空間不足,於是只好觸發下一次 Minor GC 。可以看出在這種情況下,當 Survivor 空間作為物件“出生地”的時候,很容易觸發 Minor GC ,這種 8:1 的不對稱分配不但沒能在總體上降低 Minor GC 的頻率,還會把 gc 的時間間隔搞得很不平均。把 Eden : Survivor 設成 1 : 1 也一樣,每當物件總大小滿 5 MB 的時候都必須觸發一次 Minor GC ,唯一的變化是 gc 的時間間隔相對平均了。 


上面的論述都是以“新生代使用複製演算法”這個既定事實作為前提來討論的。如果不是這樣,比如說新生代採用“標記-清除”或者“標記-整理”演算法來實現倖存物件的移動,好像確實是只需要一個 Survivor 就夠了。至於主流的虛擬機器實現為什麼不考慮採用這種方式,我也不是很清楚,或許有實現難度、記憶體碎片或者執行效率方面的考慮吧。 

為什麼2個 Survivor 空間可以達到要求?

問題很清楚了,無論 Eden 和 Survivor 的比例怎麼設定,在只有一個 Survivor 的情況下,總體上看在新生代空間滿一半的時候就會觸發一次 Minor GC 。那有沒有提升的空間呢?比如說永遠在新生代空間滿 80% 的時候才觸發 Minor GC ? 


事實上是可以做到的:我們可以設兩個 Survivor 空間( From Survivor 和 To Survivor )。比如,我們把 Eden : From Survivor : To Survivor 空間大小設成 8 : 1 : 1 ,物件總是在 Eden 區出生, From Survivor 儲存當前的倖存物件, To Survivor 為空。一次 gc 發生後: 
1)Eden 區活著的物件 + From Survivor 儲存的物件被複制到 To Survivor ; 
2) 清空 Eden 和 From Survivor ; 
3) 顛倒 From Survivor 和 To Survivor 的邏輯關係: From 變 To , To 變 From 。 

可以看出,只有在 Eden 空間快滿的時候才會觸發 Minor GC 。而 Eden 空間佔新生代的絕大部分,所以 Minor GC 的頻率得以降低。當然,使用兩個 Survivor 這種方式我們也付出了一定的代價,如 10% 的空間浪費、複製物件的開銷等。