原文鏈接 : here
根引用 Root references
一個實例死了,意味着它變得無用。只用程序員知道一個實例是否已經無用。為了讓程序知道一個實例是否已經無用,我們可以使用編譯器分析,引用計數, 或者 可達性分析。
可達性分析假設只要一個實例是可達的,它就是活着的。如果一個實例的引用直接包含在當前函數棧的一個槽(slot)中,它就是直接可達的。那些被可達實例引用的實例也是可達的。因此,可達性分析就是找出那些直接可達的引用,也就是根引用。 根引用的集合就做根集合。
當前執行函數(the mutator)的上下文中 有直接可達的數據,因此 找根集合就是在上下文中找實例的引用。當前函數的上下文引用它的棧和寄存器文件(和一些線程所有的數據)。全局數據也是直接可達的。
枚舉根集合Root set enumeration
一般情況下,如果GC使用可達性來確定一個實例是否存活,GC需要獲得當前執行現場的一個一致的快照(a consistent snapshot),以便枚舉根引用。 這對於stop-the-world 和 並發 GC(多數情況)都適用。 “一致” 意味着快照看起來就像在單個時間點上取得的。 一個一致的根引用快照對於正確性很有必要,否則一些活着的實例就可能丟失。 那現在的問題就是怎樣獲得當前上下文一致的快照。
為了獲得一致的快照,一個簡單的辦法就是在枚舉根引用的過程中,當前執行函數阻塞。 如果在枚舉過程中,根集合是不變的,快照也是一致的。
當前執行函數阻塞了它的執行以后,它就不一定能夠枚舉它上下文中的根引用,除非它在它的上下文中登記並保存了那些引用信息。也就是說,它應該能夠分辨出那個棧里有引用,哪個寄存器里有引用。 如果GC能夠准確地獲得這些信息,它就是准確根集合枚舉, 否則就是模糊的。
(對應模糊枚舉,GC使用啟發式規則來保守地猜測哪些是上下文中的引用。因此,這些GC就是所謂的保守GC。 這篇文章只討論准確枚舉。)
Harmony 通過使用GC安全點和安全區域, 支持准確根集合枚舉。
GC安全點和安全區域 Safe-point (or safepoint)
為了支持准確枚舉,JIT編譯器需要做一些額外的工作,因為只有JIT准確地知道棧幀信息和寄存器上下文。 當JIT編譯一個方法的時候, 對於每一個指令, 它都保存根引用信息,以防執行阻塞在那個指令上。
但是對每一個指令都保存那些信息太昂貴了。 它需要大量空間保存那些信息。 這是不必要的,因為 只有一小部分指令有機會在實際執行時阻塞。 JIT只需要保存那部分指令的信息就夠了-- 他們就把叫做安全點。安全點意味着對應根枚舉來說,在該點阻塞是安全的。
順便說一下,並不是所有編程語言的編譯器都能夠確切知道堆棧信息。 只有安全語言有這個能力。 比如, C/C++就沒有。
函數阻塞 Mutator suspension
對於安全點,現在的問題是,我們怎么保證函數在安全點阻塞。
有兩種基本方法來阻塞當前函數,搶先或者自願。搶先式的方法是無論任何時候GC需要進行一次收集,它都立即阻塞當前函數的執行。當它發現函數被阻塞在一個不安全的點時,它會恢復函數執行,向前滾動到一個安全點。這種方法在ORP【1】中實現,它是Harmony的前一個版本。但是,目前幾乎沒有JVM使用這種方法。
在Harmony中實現的方法是自願阻塞。當GC觸發一次垃圾回收,它只是簡單的設置一個標志; 當前執行函數會輪詢這個標志, 當發現這個標志置位的時候,他們就會阻塞。那些輪詢的點都是安全點。 多數情況下, JIT負責在合適的位置插入輪詢程序。 有時,VM也需要有一些輪詢點。
輪詢點 Polling point
那么哪里是正確輪詢GC觸發事件的地點呢? 就像我們上面討論的,我們不想在每一個指令處都設置輪詢點。 對於自願阻塞,一個更嚴重的問題是輪詢負載。因此插入輪詢點需要遵循一些基本原則:第一,輪詢點應該足夠頻繁,以便GC不需要等待當前函數阻塞的時間太長, 因為其他函數還在等着GC釋放空間來繼續執行。第二,輪詢點不能太多,導致增加運行負載過重。
最好的結果是剛好有足夠的輪詢點滿足需求:
- 一類強制的輪詢點是內存分配點。分配可以觸發一次垃圾收集,因此分配必須是安全點
- 長時間的執行總是和方法調用或者循環 有關。因此,調用點和循環回邊點也是期望的輪詢點
這些就是Harmony中的輪詢點: 分配點,調用點和循環回邊點。 多數情況下運行時負載小於1%。 不幸的是,我們發現單獨安全點是不夠的。
安全區域 Safe-region
為什么不夠呢? 因為我們忘掉了一種常見執行的情況。我們忘了它,因為它實際上不是長時間執行,而是長時間閑置。有這樣一類情況程序不能及時響應GC觸發事件,比如sleep,因系統調用阻塞。 這些操作不是JVM能夠控制的。JVM在此期間不能夠響應GC事件。 因此,我們引入了安全區域的概念來解決這個問題。
安全區域是其中引用不會改變的一段代碼片段,那么在其中任一點進行根枚舉都是安全的。 換句話說,安全區域是安全點的一個很大的擴展。
在安全點的設計中,如果GC觸發事件發生了,執行函數通過輪詢進行響應。它通過設置一個准備好的標志(ready flag)來響應。 那么GC就可以進行根枚舉了。這是一個握手協議。
安全區域也遵循這個協議。執行函數在進入安全區域時設置ready flag。在它離開安全區域以前,它先檢查GC是否完成了枚舉(或者收集),並且不再需要執行函數呆在阻塞狀態。如果是真的,它就向前執行,離開安全區域; 否則,它就像安全點一樣阻塞他自己。
在Harmony的實現中,我們插入 suspend_enable 和 suspend_disable來標志安全區域的界限。
【1】 ORP (Open Runtime Platform), http://orp.sf.net ;