1. 程式人生 > >閱讀筆記-深入理解jvm虛擬機器-2-垃圾回收演算法

閱讀筆記-深入理解jvm虛擬機器-2-垃圾回收演算法

垃圾回收演算法:

標記-清除演算法:

首先將標記出所有需要回收的物件,然後進行統一回收所有物件

基礎的回收演算法。後續的演算法基於這種思路對其不足進行改進

缺點:

效率問題,標記和清除的效率都不高。

標記清除演算法會產生大量不連續的記憶體碎片。空間碎片太多會導致當分配較大物件時,無法找到足夠的連續的記憶體從而不得不提前觸發另一次垃圾回收。

複製收集演算法:

為解決效率問題,可將記憶體按容量劃分為大小相等的兩塊,每次使用一塊,當一塊的記憶體使用完畢,將存貨的物件複製到另一塊上,然後把使用過的記憶體一次性清理掉。

現在商業虛擬機器都採用這種收集演算法回收新生代(jdk 1.7)

複製演算法在物件存活率較高時就需要進行較多的複製操作,效率也會降低

新生代百分之98的物件朝生幕死,所以並不需要按照1比1分配記憶體,而是將記憶體分為一塊較大eden區,和兩塊較小的survivor區,gc時將eden中和survivor中還存活的物件一次性複製到另外一塊survivor空間中。最後清理掉eden區和survivor區。虛擬機器中eden區和survivor區預設的大小比例是8:1。當survivor區記憶體不夠時需要依賴其他記憶體如老年代進行分配擔保

標記整理演算法:

相對複製收集演算法,如果不想浪費百分之50的空間,以應對被使用的記憶體中所有物件都百分之100存活的情況,所以在老年中一般不能直接選用複製整理演算法。

標記整理演算法標記過程仍與標記清除演算法一樣,但後續步驟不是直接對可回收物件進行清理,而是讓所有存活物件向一端移動,然後清除掉邊界的記憶體

分代收集演算法:

目前商業虛擬機器的垃圾回收都採用 分代收集 演算法,根據物件存活週期的不同將記憶體劃分為幾塊,一般的是把java堆分為新生代和老年代,這樣根據各個年代的特點採用最適合的收集演算法。在新生代中每次垃圾回收都有大批物件死去,只有少量存活,可以採用複製演算法,只需要付出少量存活物件的複製成本就可以完成收集。而在老年代中因為物件存活率高,沒有額外的空間進行分配擔保,就必須使用 標記清理 或者 標記整理 演算法來進行回收。

Hotspot演算法的實現:

列舉根節點:

可達性分析:

可達性分析從GC ROOTS節點找到引用鏈操作為例,可作為GC ROOTS的節點主要在全域性性的引用(如常量或靜態屬性)與執行上下文(例如幀棧中的本地變量表),現在很多應用僅僅是方法去就有數百兆(jdk 1.7),如果要逐個檢查這裡的引用,那麼必然要消耗很多時間

另外可達性分析對執行時間的敏感還體現在gc停頓上,因為這項工作必須在一個能確保一致性的快照中執行,這裡一致性指在整個分析期整個執行系統看起來就像被凍結在某個時間點上,不可以出現分析過程中物件引用關係還在不斷變化的情況,該點不滿足的話分析結果準確性就無法保證,這點是導致gc進行時必須停頓所有java執行緒,(sun 將之稱為 stop the world )的其中一個重要原因,即使是在號稱(幾乎)不會發生停頓的CMS收集器中,列舉根節點時也必須要停頓

目前主流的java虛擬機器使用的都是準確性gc(停頓),在系統停頓下來後,並不需要檢測完所有執行上下文和全域性的引用位置。在hotspot的視線中,是使用一組成為oopMap的資料結構達到這個目的。在類載入完成的時候,hotspot就把物件內什麼偏移量上是什麼內容計算出來,在JIT(動態編譯)過程中,在會在特定的位置記錄下棧和暫存器中哪些位置是引用,這樣gc在掃描時就可以直接得知這些資訊

在oopmap的幫助下,hotspot可以快速而準確的完成gc roots列舉,但是有一個問題:可能導致引用關係變化,或者oopmap內容變化的指令非常多,如果為每條指令都生成對應的oopmap那將會需要大量的額外空間,這樣gc成本會非常高