目錄
- 分代收集理論
- 垃圾收集區域
- 标記-清除算法
- 主要缺點
- 執行效率不穩定
- 記憶體空間碎片化問題
- 主要缺點
- 标記-複制算法
- 目的:解決前者執行效率低的問題
- 缺點:記憶體縮小一半,空間浪費
- 優化後 Appel 式回收
- 配置設定擔保
- 目的:解決前者執行效率低的問題
- 标記-整理算法
- 是否移動回收後存活的對象是一項優缺點并存的風險決策
- 如果移動存活對象
- 如果不移動和整理存活對象
- 是否移動回收後存活的對象是一項優缺點并存的風險決策
分代收集理論
垃圾收集區域
部分收集(Partial GC): 新生代收集(Minor GC/Young GC): 老年代收集(Major GC/Old GC): 混合收集(Mixed GC): 整堆收集(Full GC):收集整個Java堆和方法區的垃圾收集
标記-清除算法
最早出現也是最基礎的垃圾收集算法。算法分為"标記" 和 "清除" 兩個階段: 首先标記初所有需要回收的對象,在标記完成後,統一回收掉所有被标記的對象,也可以反過來,标記存活的對象,統一回收所有未被标記的對象。标記的過程就是對象是否屬于垃圾的判定過程。
主要缺點
執行效率不穩定
如果Java堆中包含大量對象,而且其中大部分是需要被回收的,這時必須進行大量标記和清除動作,導緻标記和清除兩個過程的執行效率都随對象數量增長而降低。
記憶體空間碎片化問題
标記、清除之後會産生大量不連續的記憶體碎片,空間碎片太多可能會導緻當以後程式運作過程中需要配置設定較大對象時無法找到足夠的連續空間而不得不提前觸發另一次垃圾收集動作。
标記-複制算法
目的:解決前者執行效率低的問題
為了解決 标記-清除 算法面對大量可回收對象時執行效率低的問題。1969年 Fenichel 提出一種稱為"半區複制"(Semispace Copying)的垃圾收集算法,它将可用記憶體按容量劃分為大小相等的兩塊,每次隻使用其中的一塊。當這一塊的記憶體用完了,就将還存活的對象複制 到另一塊上面,然後再把已使用過的記憶體空間一次性清理掉。
缺點:記憶體縮小一半,空間浪費
優化後 Appel 式回收
HotSpot虛拟機的Serial、ParNew 等新生代收集器均采用這種政策來設計新生代的記憶體布局。 Appel式回收的具體做法式把新生代分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次配置設定記憶體隻使用Eden和其中一塊Survivor。發送垃圾收集時,将Eden和Survivor中仍然存活的對象一次性複制到另一塊Surivor空間上,然後直接清理掉Eden和已用過的那塊Survivor 空間。HotSpot虛拟機預設Eden和Survivor的大小比例時8:1:1。當Survivor空間不足以容納一次Minor GC之後存貨的對象時,就需要依賴其他記憶體區域進行配置設定擔保(Handle Promotion)
标記複制算法示意圖
配置設定擔保
在發生Minor GC之前,虛拟機必須先檢查老年代最大可用的連續空間是否大于新生代所有對象總空間,如果這個條件成立,那這一次 Minor GC可以確定是安全的。如果不成立,則虛拟機會檢檢視 -XX:HandlePromotionFailure 參數的設定值是否允許擔保失敗(Handle Promotion Failure);如果允許,那會繼續檢查老年代最大可以用的連續空間是否大于曆次晉升到老年代對象的平均大小,如果大于,将嘗試一次Minor GC,盡管這次GC是有風險的;如果小于,或者參數設定false,哪就要進行一次Full GC。

标記-整理算法
應對被使用的記憶體中所有對象都100%存活的極端情況,老年代一般采用 "标記-整理"(Mark - Compact)算法,先标記,然後讓所有存活對象都向記憶體空間一端移動,然後直接清理掉邊界以外的記憶體。 标記-清除算法與标記-整理算法的本質差異在于前者是一種非移動式的回收算法,後者是移動式的。
是否移動回收後存活的對象是一項優缺點并存的風險決策
如果移動存活對象
如果移動存活對象,尤其是在老年代這種每次回收都有大量對象存活區域,移動存活對象并更新所有引用這些對象的地方将是一種極為負重的操作,并且這種對象移動操作必須全程暫停使用者應用程式才能進行。
如果不移動和整理存活對象
如果完全不考慮移動和整理存活對象的話,彌散與堆中的存活對象導緻的空間碎片化問題就隻能依賴更為複雜的記憶體配置設定器和記憶體通路器來解決。譬如通過"分區空閑配置設定連結清單"來解決記憶體配置設定問題。會額外給記憶體增加負擔,影響應用程式的吞吐量。
标記-整理算法