深入理解JVM - 垃圾收集演算法
1. 分代收集理論
當前商業VM的垃圾收集器大多遵循了分代收集 Generational Collection
理論進行設計,該理論基於一下分代假說。
- 弱分代假說 Weak Generational Hypothesis:絕大多數物件都是朝生夕滅的
- 強分代假說 Strong Generational Hypothesis:熬過越多次垃圾收集過程的物件就越難以消亡
兩個假說奠定了許多垃圾收集器一致的設計原則:收集器應該將Java堆劃分出不同的區域,然後將回收物件依據其年齡(年齡即物件熬過垃圾收集過程的次數)分配到不同的區域之中儲存。
分代收集理論的經驗法則
- 跨代引用假說 Intergenerational Reference Hypothesis: 跨代引用相對於同代引用來說僅佔極少數。
名詞
- 部分收集 Partial GC:指目標不是完整收集整個Java堆的垃圾收集
- 新生代收集 Minor GC/Young GC: 指目標只是新生代的垃圾收集
- 年代收集 Major GC/Old GC: 指目標只是老年代的垃圾收集。目前只有CMS收集器會有單獨收集老年代的行為。
- 混合收集 Mixed GC: 指目標是收集整個新生代以及部分老年代的垃圾收集。目前只有G1收集器會有這種行為
- 整堆收集 Full GC: 收集整個Java堆和方法區的垃圾收集
2. 標記-清除演算法 Mark-Sweep
先標記存活/死亡的物件,然後進行記憶體回收。
存在的問題:執行效率不穩定、記憶體碎片。
3. 標記-複製演算法
半區複製Semispace Copying: 可用記憶體分兩塊,每次使用其中一塊,GC時將存活物件移動到另一塊。
問題:記憶體浪費嚴重。
Appel式回收: 新生代記憶體分為較大的 Eden 和 兩塊較小的 Survivor,每次僅使用 Eden 和一塊 Survivor,GC時,將存活物件移動到另一塊 Survivor,然後回收 Eden 和 Survivor。一般 Eden : Survivor = 8:1。
當 Survivor 在一次 Minor GC 時不足以存放所有存活物件,,組需要其他記憶體區域進行分配擔保 Handle Promotion
4. 標記-整理 Mark-Compact
標記-複製在物件存活率較大時需要較多複製操作,比較適合新生代。
第一步同樣是標記,第二步將存活物件向記憶體一側移動,然後清理掉邊界外記憶體。
標記-清除演算法與標記-整理演算法的本質差異在於前者是一種非移動式的回收演算法,而後者是移動式的。
如果移動存活物件,尤其是在老年代這種每次回收都有大量物件存活區域,移動存活物件並更新所有引用這些物件的地方將會是一種極為負重的操作,而且這種物件移動操作必須全程暫停使用者應用 程式才能進行,像這樣的停頓被最初的虛擬機器 設計者形象地描述為Stop The World
。
但如果像標記-清除那樣,記憶體碎片又會極為嚴重。
從垃圾收集的停頓時間來看,不移動物件停頓時間會更短,甚至可以不需要停頓,但是從整個程式的吞吐量來看,移動物件會更划算。
HotSpot虛擬機器裡面關注吞吐量的Parallel Scavenge收集器是基於標記-整理演算法的,而關注延遲的CMS收集器則是基於標記-清除演算法的。
還有一種“和稀泥式”解決方案可以不在記憶體分配和訪問上增加太大額外負擔,做法是讓虛擬機器平時多數時間都採用標記-清除演算法,暫時容忍記憶體碎片的存在,直到記憶體空間的碎片化程度已經 大到影響物件分配時,再採用標記-整理演算法收集一次,以獲得規整的記憶體空間。前面提到的基於標 記-清除演算法的CMS收集器面臨空間碎片過多時採用的就是這種處理辦法。