JVM中垃圾回收機制及垃圾收集器詳解

一、垃圾收集算法

1.標記-清除算法

最基礎的收集算法是“標記-清除”(Mark-Sweep)算法,如同它的名字一樣,算法分爲“標記”和“清除”兩個階段。

①首先標記出所有需要回收的對象

②在標記完成後統一回收所有被標記的對象。

不足:

效率問題:標記和清除兩個過程的效率都不高

空間問題:標記清除之後產生大量不連續的內存碎片,空間碎片太多可能會導致以後程序運行過程中需要分配較大對象時,無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。

2.複製算法

目的: 爲了解決效率問題。

將可用內存按容量大小劃分爲大小相等的兩塊,每次只使用其中的一塊。當一塊內存使用完了,就將還存活着的對象複製到另一塊上面,然後再把已使用過的內存空間一次清理掉。這樣使得每次都是對整個半區進行內存回收,內存分配時也就不用考慮內存碎片等複雜情況。

缺點: 將內存縮小爲了原來的一半。

現代的商業虛擬機都採用這種收集算法來回收新生代,IBM公司的專門研究表明,新生代中對象98%對象是“朝生夕死”的,所以不需要按照1:1的比例來劃分內存空間,而是將內存分爲較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。HotSpot虛擬機中默認Eden和Survivor的大小比例是8:1。

3.標記-整理算法

複製收集算法在對象存活率較高時,就要進行較多的複製操作,效率就會變低。 根據老年代的特點,提出了“標記-整理”算法。

標記過程仍然與”標記-清除“算法一樣,但後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉邊界以外的內存。

4.分代收集算法

一般是把Java堆分爲新生代和老年代,這樣就可以根據各個年代的特點採用最適當的收集算法。

在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,那就選用複製算法。

在老年代中,因爲對象存活率高、沒有額外空間對它進行分配擔保,就必須採用“標記-清除”或“標記-整理”算法來進行回收。

二、垃圾回收機制的一些知識

1.JVM中的年代

JVM中分爲年輕代(Young generation)和老年代(Tenured generation)

HotSpot JVM把年輕代分爲了三部分:1個Eden區和2個Survivor區(分別叫from和to)。默認比例爲8:1,爲啥默認會是這個比例,接下來我們會聊到。

一般情況下,新創建的對象都會被分配到Eden區(一些大對象特殊處理),這些對象經過第一次Minor GC後,如果仍然存活,將會被移到Survivor區。對象在Survivor區中每熬過一次Minor GC,年齡就會增加1歲,當它的年齡增加到一定程度時,就會被移動到年老代中。 因爲年輕代中的對象基本都是朝生夕死的(80%以上),所以在年輕代的垃圾回收算法使用的是複製算法,複製算法的基本思想就是將內存分爲兩塊,每次只用其中一塊,當這一塊內存用完,就將還活着的對象複製到另外一塊上面。複製算法不會產生內存碎片。

在GC開始的時候,對象只會存在於Eden區和名爲“From”的Survivor區,Survivor區“To”是空的。緊接着進行GC,Eden區中所有存活的對象都會被複制到“To”,而在“From”區中,仍存活的對象會根據他們的年齡值來決定去向。年齡達到一定值(年齡閾值,可以通過-XX:MaxTenuringThreshold來設置)的對象會被移動到年老代中,沒有達到閾值的對象會被複制到“To”區域。經過這次GC後,Eden區和From區已經被清空。這個時候,“From”和“To”會交換他們的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎樣,都會保證名爲To的Survivor區域是空的。Minor GC會一直重複這樣的過程,直到“To”區被填滿,“To”區被填滿之後,會將所有對象移動到年老代中。

2.Minor GC和Full GC的區別

Minor GC:指發生在新生代的垃圾收集動作,該動作非常頻繁。

Full GC/Major GC:指發生在老年代的垃圾收集動作,出現了Major GC,經常會伴隨至少一次的Minor GC。Major GC的速度一般會比Minor GC慢10倍以上。

3. 空間分配擔保

在發生Minor GC之前,虛擬機會先檢查老年代最大可用的連續空間是否大於新生代所有對象的總空間,如果這個條件成立,那麼Minor GC可以 確保是安全的。如果不成立,則虛擬機會查看HandlePromotionFailure設置值是否允許擔保失敗。如果允許,那會繼續檢查老年代最大可用的連續空間是否大於歷次晉升到老年代對象的平均大小,如果大於,則將嘗試進行一次Minor GC,儘管這個Minor  GC是有風險的。如果小於,或者HandlePromotionFailure設置不允許冒險,那這時也要改爲進行一次Full GC。

以上便是在垃圾回收過程中,需要了解的一些必要的知識。下面我們就來介紹具體的垃圾收集器。

三、垃圾收集器

上圖展示了7種作用於不同分代的收集器,如果兩個收集器之間存在連線,說明它們可以搭配使用。

1.Serial收集器

 是最基本、發展歷史最悠久的收集器。這是一個單線程收集器。但它的“單線程”的意義並不僅僅說明它只會使用一個CPU或一條收集線程去完成垃圾收集工作,更重要的是它在進行垃圾收集時,必須暫停其他所有的工作線程,直到它收集結束。

是虛擬機運行在Client模式下的默認新生代收集器。

優勢: 簡單而高效(與其他收集器的單線程比),對於限定單個CPU的環境來說,Serial收集器由於沒有線程交互的開銷,專心做垃圾收集自然可以獲得最高的單線程效率。

2.ParNew收集器

ParNew收集器其實就是Serial收集器的多線程版本。

是許多運行在Server模式下的虛擬機中首選的新生代收集器,其中一個與性能無關但很重要的原因是,除了Serial收集器外,目前只有它能與CMS收集器配合工作。

ParNew收集器默認開啓的收集線程數與CPU的數量相同。 下圖是ParNew/Serial Old收集器運行示意圖

3.Parallel Scavenge收集器

Parallel Scavenge收集器是一個新生代收集器,使用複製算法,又是並行的多線程收集器。

最大的特點是: Parallel Scavenge收集器的目標是達到一個可控制的吞吐量。

所謂吞吐量就是CPU用於運行用戶代碼的時間與CPU總消耗時間的比值,即吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間)。

高吞吐量則可以高效率地利用CPU時間,儘快完成程序的運算任務,主要適合在後臺運算而不需要太多交互的任務。

4.Serial Old收集器

Serial Old是Serial收集器的老年代版本,同樣是一個單線程收集器,使用“標記-整理”算法。這個收集器的主要意義也是在於給Client模式下虛擬機使用。

如果在Server模式下,它主要還有兩大用途

1.與Parallel Scavenge收集器搭配使用

2.作爲CMS收集器的後備預案,在併發收集發生Conurrent Mode Failure使用。

5.Parallel Old收集器

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多線程和“標記-整理”算法。

在注重吞吐量以及CPU資源敏感的場合,都可以優先考慮Parallel Scavenge+Parallel Old收集器

6.CMS(Concurrent Mark Sweep)收集器

是HotSpot虛擬機中第一款真正意義上的併發收集器,它第一次實現了讓垃圾收集線程與用戶線程同時工作。

關注點: 儘可能地縮短垃圾收集時用戶線程的停頓時間。

CMS收集器是基於“標記-清除”算法實現的,整個過程分爲4個步驟

①初始標記

②併發標記

③重新標記

④併發清除

其中,初始標記,重新標記這兩個步驟仍然需要“Stop The World”。初始標記僅僅只標記一下GC Roots能直接關聯到的對象,速度很快。併發標記階段就是 進行GC Roots Tracing的過程。

重新標記階段則是爲了修正併發標記期間因用戶程序繼續運作而導致標記產生變動的那一部分對象的標記機率,這個階段的停頓時間一般會比初始標記階段稍長,但遠比並發標記時間短。

整個過程耗時最長的階段是併發標記,併發清除過程,但這兩個過程可以和用戶線程一起工作。

缺點:

①CMS收集器對CPU資源非常敏感。在併發階段,它雖然不會導致用戶線程停頓,但是會因爲佔用了一部分線程(或者說CPU資源)而導致應用程序變慢,總吞吐量會降低。

②CMS收集器無法處理浮動垃圾,可能出現“Conurrent Mode Failure”失敗而導致另一次Full GC的產生。由於CMS併發清理階段用戶線程還在運行着,伴隨程序運行自然就還會產生新的垃圾,這一部分垃圾出現在標記過程之後,CMS無法在檔次收集中處理掉它們,只好留待下一次GC時再清理掉。這部分垃圾就稱爲“浮動垃圾”。因此CMS收集器不能像其他收集器那樣等到老年代幾乎完全被填滿了再進行收集,需要預留一部分空間提供併發收集時程序運作使用。在JDK1.5的默認設置下,CMS收集器當老年代使用了68%的空間後就會被激活。如果預留空間無法滿足程序需要,就會出現一次“Concurrent Mode Failure”失敗,這時虛擬機將啓動後備預案Serial Old。

③CMS是一款基於“標記-清除”算法實現的收集器,所以會有大量空間碎片問題。

7.G1收集器

是當今收集器技術發展的最前沿成果之一。是一款面向服務端應用的垃圾收集器。

特點:

①並行與併發

能充分利用多CPU,多核環境下的硬件優勢,縮短Stop-The-World停頓的時間,同時可以通過併發的方式讓Java程序繼續執行

②分代收集

可以不需要其他收集器的配合管理整個堆,但是仍採用不同的方式去處理分代的對象。

③空間整合

G1從整體上來看,採用基於“標記-整理”算法實現收集器

G1從局部上來看,採用基於“複製”算法實現。

④可預測停頓

使用G1收集器時,Java堆內存佈局與其他收集器有很大差別,它將整個Java堆劃分成爲多個大小相等的獨立區域。 G1跟蹤各個Region裏面的垃圾堆積的價值大小(回收所獲得的空間大小以及回收所需時間的經驗值),在後臺維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的Region。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章