前言
Github:https://github.com/yihonglei/jdk-source-code-reading(java-jvm)
JVM記憶體結構
JVM類加載機制
JVM記憶體溢出分析
HotSpot對象建立、記憶體、通路
JVM垃圾回收機制(1)--如何判定對象可以回收
JVM垃圾回收機制(2)--垃圾收集算法
JVM垃圾回收機制(3)--垃圾收集器
JVM垃圾回收機制(4)--記憶體配置設定和回收政策
這裡主要分析"标記-清除"算法、"複制"算法、"标記-整理"算法、"分代收集"算法的思想、
優缺點和應用場景。
一 标記-清除算法
1、算法思路
"标記-清除"(Mark-Sweep)算法是最基礎的收集算法,之是以叫做最基礎的收集算法,
是因為很多收集算法都是基于這種該算法思想對其不足進行改進得到的。顧名思義,
"标記-清除"算法分為"标記"和"清除"兩個階段實作。
1)标記
首先标記出所有需要回收的對象,要宣告一個對象死亡,至少要經曆兩次标記過程。
第一次标記
如果對象在進行可達性分析後發現沒有與 GC Roots 相連接配接的引用鍊,那它将會被第一次标記
并且進行一次篩選,篩選的條件是此對象是否有必要執行 finalize() 方法。當對象沒有覆寫
finalize() 方法,或者 finalize() 方法已經被虛拟機調用過,虛拟機将這兩種情況都視為
"沒有必要執行"。如果這個對象被判定為有必要執行 finalize() 方法,那麼這個對象将會被
放置在一個叫做 F-Queue 的隊列中,并在稍後由一個虛拟機自動建立的、低優先級的
Finalizer 線程去觸發這個方法。
第二次标記
GC 将對 F-Queue 隊列中的對象進行第二次小規模标記;finalize() 方法是對象逃脫死亡的
最後一次機會:如果對象在其 finalize() 方法中重新與引用鍊上任何一個對象建立關聯,
第二次标記時會将其移出"即将回收"的集合;如果對象沒有,也可以認為對象已死,可以回收了;
2)清除
兩次标記後,還在"即将回收"集合的對象将被統一回收。
2、算法優點
實作簡單。
3、算法缺點
該算法主要有兩個缺陷,一個是效率問題,另外一個空間問題。
1)效率問題
标記和清除兩個過程的效率都不高。
2)空間問題
标記清除之後會産生大量的不連續的記憶體碎片,空間碎片太多可能導緻以後在程式運作
過程中需要配置設定較大對象時,無法找到足夠的連續記憶體而不得不提前觸發另外一次垃圾收集動作。
4、算法應用場景
針對 CMS 收集器使用。
二 複制算法
"複制"(Copying)算法的出現,是為了解決"标記-清除"算法的效率問題。
1、算法思路
1)将可用記憶體按照容量劃分為大小相等的兩塊,每次隻使用其中一塊。
2)當一塊記憶體用完後,就将還存活着的對象複制到另外一塊上面,然後再把已使用多的
記憶體空間一次清掉。這樣使得每次都對整個半區進行記憶體回收,記憶體配置設定時也就不用考慮
記憶體碎片等複雜情況,隻要移動堆頂指針,按順序配置設定記憶體即可,實作簡單,運作高效。
2、算法優點
不會産生記憶體碎片,記憶體配置設定實作簡單,高效。
3、算法缺點
1)空間浪費
可用記憶體縮減為原來的一半,太過浪費(解決:可以改良,不按1:1比例劃分);
2)效率随對象存活率升高而變低
當對象存活率較高時,需要進行較多複制操作,效率将會變低;
4、算法應用場景
現在商業虛拟機都采用這種收集算法來回收新生代,用該算法的垃圾收集器比較多,
如 Serial 收集器、ParNew 收集器、Parallel Scavenge 收集器、G1(從局部看)。
5、HotSpot對空間浪費的改良算法
1)弱代理論
分代垃圾收集基于弱代理論(weak generational hypothesis),具體描述如下:
- 大多數配置設定了記憶體的對象并不會存活太長時間,在處于年輕代時就會死掉;
- 很少有對象會從老年代變成年輕代;
IBM 研究表明:新生代中 98% 的對象都是"朝生夕死",是以不需要按照 1:1 的比例來劃
分記憶體空間;
2)HotSpot虛拟機新生代記憶體布局及算法
- 将新生代記憶體分為一塊較大的 Eden 空間和兩塊較小的 Survivor 空間;
- 每次使用 Eden 和其中一塊 Survivor;
- 當回收時,将 Eden 和使用中的 Survivor 中還存活的對象一次性複制到另外一塊Survivor;
- 而後清理掉 Eden 和使用過的 Survivor 空間;
- 然後就就接着使用 Eden 和那一塊 Survivor 空間,每次重複回收時邏輯;
注意:HotSpot 預設虛預設 Eden 和 Survivor 的大小比例是 8:1,也就是每次新生代中
可用記憶體空間為整個新生代容量的 90%(Eden 80% + 一個Survivor 10%),隻有 10%
的記憶體會被"浪費"掉。
3)配置設定擔保
如果另一塊 Survivor 空間沒有足夠空間存放上一次新生代收集下來的存活對象時,
這些對象将直接通過配置設定擔保機制(Handle Promotion)進入老年代;
三 标記-整理算法
複制收集算法在對象存活率較高時就要進行較多的複制操作,效率會降低。更關鍵的是,
如果不想浪費 50%的空間,就需要額外的空間進行配置設定擔保,以應對被使用的記憶體中所有
對象都 100% 存活的極端情況,是以在老年代一般不直接選用這種算法。
1、算法思路
"标記-整理"(Mark-Compact)算法的标記過程仍然與"标記-清除"算法一樣,但後續步驟
不是直接對回收對象進行清理,而是讓所有存活的對象都向一端移動,然後清理掉端邊界
以外的記憶體,"标記-整理"算法的執行示意圖如下:
2、算法優點
1)不會像複制算法,效率随對象存活率升高而變低。
老年代特點:
對象存活率高,沒有額外的空間可以配置設定擔保;是以老年代一般不能直接選用複制算法;
而選用标記-整理算法;
2)不會像标記-清除算法,産生記憶體碎片因為清除前,進行了整理,存活對象都集中到空間一側;
3、算法缺點
主要是效率問題:除像标記-清除算法的标記過程外,還多了需要整理的過程,效率更低;
4、算法應用場景
很多垃圾收集器采用這種算法來回收老年代;如 Serial Old 收集器、G1(從整體看);
四 分代收集算法
"分代收集"(Generational Collection)算法結合不同的收集算法處理不同區域。
1、算法思路
目前虛拟機的垃圾收集都采用"分代收集"算法,這種算法并沒有什麼新的思想,隻是根據對象的
存活申請周期的不同将記憶體劃分為幾塊,這樣就可以根據各個年代的特點采用最适當的收集算法。
Java 堆分為新生代和老年代。
1)新生代
在新生代中,每次垃圾收集都發現有大批量對象死去,隻有少量存活,就選用複制算法,
隻需要付出少量的存活對象的複制成本就可以完成收集。
2)老年代
在老年代中對象存活率高、沒有額外空間對它進行配置設定擔保,就必須使用"标記-清除"
或"标記-整理"算法來進行回收。
HotSpot 虛拟機對新生代和老年代一般的記憶體劃分示意圖:
2、算法優點
可以根據各個年代的特點采用最适當的收集算法;
3、算法缺點
仍然不能控制每次垃圾收集的時間;
4、算法應用場景
目前幾乎所有商業虛拟機的垃圾收集器都采用分代收集算法;如 HotSpot 虛拟機中全部
垃圾收集器:Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、
G1(也保留);
參考文獻
《深入了解Java虛拟機》 (第二版) 周志明 著;