在《Jvm垃圾回收器(基礎篇)》中我們主要學習了判斷對象是否存活還是死亡?兩種基礎的垃圾回收算法:引用計數法、可達性分析算法。以及Java引用的4種分類:強引用、軟引用、弱引用、虛引用。和方法區的回收介紹。
那么接下來我們重點研究下虛擬機的幾種常見的垃圾回收算法:標記-清除算法、復制算法、標記-整理算法、分代收集算法。
一:標記-清除算法
最基礎的收集算法,總共分為‘ 標記 ’和‘ 清除 ’兩個階段
1.標記
標記出所有需要回收的對象
在《Jvm垃圾回收器(基礎篇)》中說明了判斷對象是否回收需要兩次標記,現在我們再來回顧一下
一次標記:在經過可達性分析算法后,對象沒有與GC Root相關的引用鏈,那么則被第一次標記。並且進行一次篩選:當對象有必要執行finalize()方法時,則把該對象放入F-Queue隊列中。
二次標記:對F-Queue隊列中的對象進行二次標記。在執行finalize()方法時,如果對象重新與GC Root引用鏈上的任意對象建立了關聯,則把他移除出“ 即將回收 ”集合。否則就等着被回收吧!!!
對被第一次標記切被第二次標記的,就可以判定位可回收對象了。
2.清除
兩次標記后,還在“ 即將回收 ”集合的對象進行回收。
執行過程如下:

優點:基礎最基礎的可達性算法,后續的收集算法都是基於這種思想實現的
缺點:標記和清除效率不高,產生大量不連續的內存碎片,導致創建大對象時找不到連續的空間,不得不提前觸發另一次的垃圾回收。
二:復制算法
將可用內存按容量分為大小相等的兩塊,每次只使用其中一塊,當這一塊的內存用完了,就將還存活的對象復制到另外一塊內存上,然后再把已使用過的內存空間一次清理掉。
復制算法執行過程如下:

優點:實現簡單,效率高。解決了標記-清除算法導致的內存碎片問題。
缺點:代價太大,將內存縮小了一半。效率隨對象的存活率升高而降低。
現在的商業虛擬機都采用這種算法(需要改良1:1的缺點)來回收新生代。
2.1 HotSpot虛擬機的改良算法
1.弱代理論
分代垃圾收集基於弱代理論。具體描述如下:
- 大多說分配了內存的對象並不會存活太長時間,在處於年輕時代就會死掉。
- 很少有對象會從老年代變成年輕代。
其中IBM研究表明:新生代中98%的對象都是"朝生夕死"; 所以並不需要按1:1比例來划分內存(解決了缺點1);
2.Hotspot虛擬機新生代內存布局及算法
新生代內存分配一塊較大的Eden空間和兩塊較小的Survivor空間
每次使用Eden和其中一塊Survivor空間
回收時將Eden和Survivor空間中存活的對象一次性復制到另一塊Survivor空間上
最后清理掉Eden和使用過的Survivor空間。
Hotspot虛擬機默認Eden和Survivor的大小比例是8:1。
分配擔保
如果另一塊Survivor空間沒有足夠內存來存放上一次新生代收集下來的存活對象,那么這些對象則直接通過擔保機制進入老年代。
關於分配擔保的內容,我會在講述垃圾收集器時詳細描述。
三:標記-整理算法
標記-整理算法是根據老年代的特點應運而生。
3.1 標記
標記過程和標記-清理算法一致(也是基於可達性分析算法)。
3.2 整理
和標記-清理不同的是,該算法不是針對可回收對象進行清理,而是根據存活對象進行整理。讓存活對象都向一端移動,然后直接清理掉邊界以外的內存。
標記-整理算法示意圖

優點:不會像復制算法那樣隨着存活對象的升高而降低效率,不像標記-清除算法那樣產生不連續的內存碎片
缺點:效率問題,除了像標記-清除算法的標記過程外,還多了一步整理過程,效率更低。
四:分代收集算法
當前商業虛擬機的垃圾收集都是采用“ 分代收集 ”算法。
根據對象存活周期的不同將內存划分為幾塊。一般把java堆分為新生代和老年代。JVM根據各個年代的特點采用不同的收集算法。
新生代中,每次進行垃圾回收都會發現大量對象死去,只有少量存活,因此比較適合復制算法。只需要付出少量存活對象的復制成本就可以完成收集。
老年代中,因為對象存活率較高,沒有額外的空間進行分配擔保,所以適合標記-清理、標記-整理算法來進行回收。
----對《深入理解Java虛擬機》第3章垃圾收集器與內存分配策略 3.3小節總結。接下來總結3.5垃圾收集器。
參考:
《深入理解Java虛擬機》
https://blog.csdn.net/tjiyu/article/details/53983064
