JVM(四)垃圾回收的實現演算法和執行細節

王磊的部落格發表於2019-01-25

全文共 1890 個字,讀完大約需要 6 分鐘。

上一篇我們講了垃圾標記的一些實現細節和經典演算法,而本文將系統的講解一下垃圾回收的經典演算法,和Hotspot虛擬機器執行垃圾回收的一些實現細節,比如安全點和安全區域等。

因為各個平臺的虛擬機器操作記憶體的方法各不相同,且牽扯大量的程式實現細節,所以本文不會過多的討論演算法的具體實現,只會介紹幾種演算法思想及發展過程。

垃圾回收演算法

1、標記-清除演算法

標記-清除演算法是最基礎的演算法,像它的名字一樣演算法分為“標記”和“清除”兩個階段,首先需要標記出所需要回收的物件,標記完成後統一收集被標記的物件。

優點: 實現簡單。

缺點: 產生不連續的記憶體碎片;“標記”和“清除”的執行效率都不高。

標記-清除演算法執行過程圖:

標記-清除演算法

(本文圖片來自《深入理解Java虛擬機器》)

2、複製演算法

複製演算法就是將記憶體分為大小相同的兩塊,當這一塊使用完了,就把當前存活的物件複製到另一塊,然後一次性清空當前區塊。

優點: 執行效率高。

缺點: 空間利用率低, 因為複製演算法每次只能使用一半的記憶體。

複製演算法

3、標記-整理演算法

也稱標記-壓縮演算法,標記-整理演算法採用和標記清除演算法一樣的物件“標記”,但後續不會對可回收物件進行清理,而是將存活的物件往一端空閒空間移動,然後清理邊界以外的記憶體空間。

優點: 解決了記憶體碎片問題,比複製演算法空間利用率高。

缺點: 因為有區域性物件移動,相對效率不高。

標記-整理演算法執行過程圖:

標記-整理演算法

4、分代收集演算法

目前商用虛擬機器都採用的是分代收集的演算法,這種演算法按照物件存活週期把記憶體分為幾塊,一般Java中分為新生代和老年代。把存活率低的物件分到新生代使用複製演算法提高垃圾回收的效能,老年代則存放存活率搞的物件,使用標記-清除和標記-整理的演算法,提高記憶體空間使用率。

新生代和老生代的具體介紹和引數配置,後續的文章會詳細講解。

垃圾回收執行細節

本節將詳細的介紹一下HotSpot虛擬機器在執行垃圾回收時的一些細節,目的是讓讀者更好的理解Java虛擬機器。

HotSpot虛擬機器: 它是Sun JDK和OpenJDK自定的虛擬機器,也是目前使用最廣泛的虛擬機器。

垃圾回收流程: Java虛擬機器在記憶體回收之前,為了保證記憶體的一致性,必須先暫停程式的執行,也就是傳說中的Stop The World(簡稱STW),在使用可達性分析演算法列舉GC Roots,標記出死亡物件,再進行垃圾回收。

垃圾回收遇到的問題: 那既然是要暫停程式的執行,就一定要保證停止的時間足夠短,並且可控,不然帶來的災難將是毀滅性的。

解決方案: 顯然HotSpot在設計的時候也考慮到了這個問題,所以在JIT編譯的時候就會使用OopMap資料結構來記錄棧和暫存器上的引用,這樣虛擬機器就直接知道了那些地方存放著物件的引用,如下圖,為我編譯String.hashCode()方法的部分原生程式碼:

String.hashCode原生程式碼

可以看出,使用OopMap資料結構儲存了普通物件的指標引用。

檢視彙編的方法,啟動命令列執行: java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly YouTestClass

命令可能會報錯: Could not load hsdis-amd64.dll; library not loadable; PrintAssembly is disabled

報錯解決方法:使用編譯好的hsdis.dll放到:jre安裝目錄\bin\server目錄下即可,hsdis.dll地址地址:pan.baidu.com/s/1-D6u0gnU…

安全點(Safepoint)

在OopMap的協助下,HotSpot可以快速的完成GC Roots列舉,但導致OopMap內容變化的指令很多,而且如果給每個物件生成對應的OopMap,會造成大量額外的空間,這會導致GC成本很高,所以HotSpot只會在“特定的位置”生成對應的OopMap,這些位置就成為“安全點”。

HotSpot也並不是任何時刻都會停頓下來進行GC,只會在程式都到底安全點之後才會GC,所以安全點的設定不能太少,讓GC等待時間太長,也不能太多增大執行時的成本。

安全點的兩種執行緒中斷方式

搶斷式中斷:不需要執行緒的執行程式碼去主動配合,當發生GC時,先強制中斷所有執行緒,然後如果發現某些執行緒未處於安全點,恢復程式執行,直到進入安全點為止。

主動式中斷:不強制中斷執行緒,只是簡單地設定一箇中斷標記,各個執行緒在執行時輪詢這個標記,一旦發現標記被改變(出現中斷標記)時,那麼將執行到安全點後自己中斷掛起。目前所有商用虛擬機器全部採用主動式中斷。

安全區域(Saferegion)

安全點機制僅僅是保證了程式執行時不需要太長時間就可以進入一個安全點進行 GC 動作,但是當特殊情況時,比如執行緒休眠、執行緒阻塞等狀態的情況下,顯然HotSpot不可能一直等待被阻塞或休眠的執行緒正常喚醒執行;此時就引入了安全區的概念。

安全區(Saferegion):安全區域是指在一段區域內,物件引用關係等不會發生變化,在此區域內任意位置開始GC都是安全的;執行緒執行時,首先標記自己進入了安全區,然後在這段區域內,如果執行緒發生了阻塞、休眠等操作,HotSpot發起GC時將忽略這些處於安全區的執行緒。當執行緒再次被喚醒時,首先他會檢查是否完成了GC Roots列舉(或這個GC過程),如果完成了就繼續執行,否則將繼續等待直到收到可以安全離開的Safe Region的訊號為止。

參考

《深入理解Java虛擬機器》

《垃圾回收的演算法與實現》

最後

關注公眾號,傳送“gc”關鍵字,領取《垃圾回收的演算法與實現》學習資料。

JVM(四)垃圾回收的實現演算法和執行細節

JVM(四)垃圾回收的實現演算法和執行細節

相關文章