深刻理解java虛擬機——HotSpot的算法實現

1.枚舉根節點java

檢查對象是否被引用須要根據GC Roots節點來查找引用鏈。可做爲GC Roots的節點主要是全局性的引用與執行上下文中,若是要逐個檢查引用,必然消耗時間。
另外可達性分析對執行時間的敏感還體如今GC停頓上,由於這項分析工做必須在一個能確保一致性的快照中進行——這裏的「一致性」的意思是指整個分析期間整個系統執行系統看起來就行被凍結在某個時間點,不能夠出現分析過程當中對象引用關係還在不斷變化的狀況,該點不知足的話分析結果的準確性就沒法獲得保證。這點是致使GC進行時必須暫停全部Java執行線程的其中一個重要緣由。安全

目前主流的java虛擬機使用的都是準確式GC,因此當執行系統停頓下來後,並不須要一個不漏地檢查完全部執行上下文和全局的引用位置,虛擬機應當是有辦法直接獲得哪些地方存放着對象引用。在HotSpot的實現中,是使用一組成爲OopMap的數據結構來達到這個目的,在類加載完成的時候,HotSpot就把對象內什麼偏移量上是什麼類型的數據計算出來,在JIT編譯過程當中,也會在特定的位置記錄下棧裏和寄存器裏哪些位置是引用。這樣GC在掃描時就就能夠直接得知這些信息了。數據結構

對OopMap(Ordinary Object Pointer Map)的理解,引自http://dsxwjhf.iteye.com/blog/2201685線程

OopMap 用於枚舉 GC Roots ; 
RememberedSet 用於可達性分析。 

OopMap 

OopMap 記錄了棧上本地變量到堆上對象的引用關係。其做用是:垃圾收集時,收集線程會對棧上的內存進行掃描,看看哪些位置存儲了 Reference 類型。若是發現某個位置確實存的是 Reference 類型,就意味着它所引用的對象這一次不能被回收。但問題是,棧上的本地變量表裏面只有一部分數據是 Reference 類型的(它們是咱們所須要的),那些非 Reference 類型的數據對咱們而言毫無用處,但咱們仍是不得不對整個棧所有掃描一遍,這是對時間和資源的一種浪費。 
一個很天然的想法是,能不能用空間換時間,在某個時候把棧上表明引用的位置所有記錄下來,這樣到真正 gc 的時候就能夠直接讀取,而不用再一點一點的掃描了。事實上,大部分主流的虛擬機也正是這麼作的,好比 HotSpot ,它使用一種叫作 OopMap 的數據結構來記錄這類信息。 
咱們知道,一個線程意味着一個棧,一個棧由多個棧幀組成,一個棧幀對應着一個方法,一個方法裏面可能有多個安全點。 gc 發生時,程序首先運行到最近的一個安全點停下來,而後更新本身的 OopMap ,記下棧上哪些位置表明着引用。枚舉根節點時,遞歸遍歷每一個棧幀的 OopMap ,經過棧中記錄的被引用對象的內存地址,便可找到這些對象( GC Roots )。 
經過上面的解釋,咱們能夠很清楚的看到使用 OopMap 能夠避免全棧掃描,加快枚舉根節點的速度。但這並非它的所有用意。它的另一個更根本的做用是,能夠幫助 HotSpot 實現準確式 GC (我的感受這纔是 OopMap 被設計出來的根本緣由,提升 GC Roots Enumeration 速度更像是一個「意外的驚喜」)。關於準確式 GC 的具體內容(如:什麼叫準確式 GC ?什麼叫保守式 GC ?什麼叫半保守式 GC ?準確式 GC 有哪些實現思路?等等),在此不一一說明,你們能夠參考 找出棧上的指針/引用 這篇文章。須要說明的是,該文章的做者是 Oracle HotSpot 虛擬機團隊的開發人員。 

RememberedSet 

RememberedSet 用於處理這類問題:好比說,新生代 gc (它發生得很是頻繁)。通常來講, gc 過程是這樣的:首先枚舉根節點。根節點有可能在新生代中,也有可能在老年代中。這裏因爲咱們只想收集新生代(換句話說,不想收集老年代),因此沒有必要對位於老年代的 GC Roots 作全面的可達性分析。但問題是,確實可能存在位於老年代的某個 GC Root,它引用了新生代的某個對象,這個對象你是不能清除的。那怎麼辦呢? 

仍然是拿空間換時間的辦法。事實上,對於位於不一樣年代對象之間的引用關係,虛擬機會在程序運行過程當中給記錄下來。對應上面所舉的例子,「老年代對象引用新生代對象」這種關係,會在引用關係發生時,在新生代邊上專門開闢一塊空間記錄下來,這就是 RememberedSet 。因此「新生代的 GC Roots 」 + 「 RememberedSet 存儲的內容」,纔是新生代收集時真正的 GC Roots 。而後就能夠以此爲據,在新生代上作可達性分析,進行垃圾回收。 

咱們知道, G1 收集器使用的是化整爲零的思想,把一塊大的內存劃分紅不少個域( Region )。但問題是,不免有一個 Region 中的對象引用另外一個 Region 中對象的狀況。爲了達到能夠以 Region 爲單位進行垃圾回收的目的, G1 收集器也使用了 RememberedSet 這種技術,在各個 Region 上記錄自家的對象被外面對象引用的狀況。設計

2.安全點指針

爲了空間成本考慮,HotSpot沒有爲每條指令都生成OopMap,只是在特定的位置記錄了這些信息,這些位置稱爲安全點(Safepoint)。程序只有在到達安全點是才能停下來開始GC。安全點的設定基本上是以程序「是否具備讓程序長時間執行的特徵」爲標準進行選定的,長時間執行的最明顯特徵就是指令序列服用,例如 方法調用、循環跳轉、異常跳轉等。對象

如何在GC發生時讓全部的線程都到達安全點上停頓下來:搶先式中斷(Preemptive Suspension)和主動式中斷(Voluntary Suspension)。blog

搶先式中斷:不須要線程的執行代碼去主動配合,當發生 GC 時,先強制中斷全部線程,而後若是發現某些線程未處於安全點,那麼將其喚醒,直至其到達安全點再次將其中斷;這樣一直等待全部線程都在安全點後開始 GC。遞歸

主動式中斷:不強制中斷線程,只是簡單地設置一箇中斷標記,各個線程在執行時輪詢這個標記,一旦發現標記被改變(出現中斷標記)時,那麼將運行到安全點後本身中斷掛起;目前全部商用虛擬機所有采用主動式中斷。內存

3.安全區域

Safepoint機制保證了程序執行時,在不太長的時間會進入GC的Safepoint,但若是線程處於Sleep狀態或者Blocked狀態,這種狀況須要安全區域(Safe Region)來解決。

安全區域是指在一段區域內,對象引用關係等不會發生變化,在此區域內任意位置開始 GC 都是安全的;線程運行時,首先標記本身進入了安全區,而後在這段區域內,若是線程發生了阻塞、休眠等操做,JVM 發起 GC 時將忽略這些處於安全區的線程。當線程再次被喚醒時,首先他會檢查是否完成了 GC Roots枚舉(或這個GC過程),而後選擇是否繼續執行,不然將繼續等待 GC 的完成。

相關文章
相關標籤/搜索