GC(Garbage Collection)即Java垃圾回收機制,是Java與C++的主要區別之一,做爲Java開發者,通常不須要專門編寫內存回收和垃圾清理代碼,對內存泄露和溢出的問題,也不須要像C++程序員那樣戰戰兢兢,就是由於Java有這個方便的機制。程序員
爲了對GC有一個直觀的認識,先來一張圖:
對圖中各類名詞不熟悉的話,請參照個人上一篇文章:JVM小結算法
JVM在進行GC時,並不是每次都對上面三個內存區域一塊兒回收的,大部分時候回收的都是指新生代。所以GC按照回收的區域又分了兩種類型,一種是普通GC(minor GC),一種是全局GC(major GC or Full GC)segmentfault
年輕代中使用的是Minor GC,這種GC算法採用的是複製算法(Copying)。數組
此圖表明瞭堆的內存結構併發
HotSpot JVM把年輕代分爲了三部分:1個Eden區和2個Survivor區(分別叫from和to)。默認比例爲8:1: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
區被填滿以後,會將全部對象移動到年老代中。佈局
-XX:MaxTenuringThreshold
設置對象在新生代中存活的次數
由於Eden區對象通常存活率較低,通常的,使用兩塊10%的內存做爲空閒和活動區間,而另外80%的內存,則是用來給新建對象分配內存的。一旦發生GC,將10%的from活動區間與另外80%中存活的eden對象轉移到10%的to空閒區間,接下來,將以前90%的內存所有釋放,以此類推。 spa
劣勢:
複製算法彌補了標記/清除算法中,內存佈局混亂的缺點。不過與此同時,它的缺點也是至關明顯的:線程
老年代通常是由標記清除或者是標記清除與標記整理的混合實現。設計
當堆中的有效內存空間(available memory)被耗盡的時候,就會中止整個程序(也被稱爲stop the world),而後進行兩項工做,第一項則是標記,第二項則是清除。code
通俗來說,就是當程序運行期間,若可使用的內存被耗盡的時候,GC線程就會被觸發並將程序暫停,隨後將依舊存活的對象標記一遍,最終再將堆中全部沒被標記的對象所有清除掉,接下來便讓程序恢復運行。
缺點:
老年代通常是由標記清除或者是標記清除與標記壓縮的混合實現。
在整理壓縮階段,再也不對標記的對像作回收,而是經過全部存活對像都向一端移動,而後直接清除邊界之外的內存。
能夠看到,標記的存活對象將會被整理,按照內存地址依次排列,而未被標記的內存會被清理掉。如此一來,當咱們須要給新對象分配內存時,JVM只須要持有一個內存的起始地址便可,這比維護一個空閒列表顯然少了許多開銷。
標記/整理算法不只能夠彌補標記/清除算法當中,內存區域分散的缺點,也消除了複製算法當中,內存減半的高額代價。
劣勢:標記/整理算法惟一的缺點就是效率也不高,不只要標記全部存活對象,還要整理全部存活對象的引用地址。從效率上來講,標記/整理算法要低於複製算法。
這種算法最直接簡單,它維護了一個引用計數器,對象被引用一次計數器+1,少一次-1,若是計數器爲0則對象就被視爲垃圾進行回收。
注:JVM的實現通常不採用這種方式。
劣勢:
內存效率:複製算法>標記清除算法>標記整理算法(此處的效率只是簡單的對比時間複雜度,實際狀況不必定如此)。
內存整齊度:複製算法==標記整理算法>標記清除算法。
內存利用率:標記整理算法==標記清除算法>複製算法。
能夠看出,效率上來講,複製算法最快,可是卻浪費了太多內存,而爲了儘可能兼顧上面所提到的三個指標,標記/整理算法相對來講更平滑一些,但效率上依然不盡如人意,它比複製算法多了一個標記的階段,又比標記/清除多了一個整理內存的過程。
有沒有最好的算法呢?只能說:沒有最好的,只有最適合的——分代收集:
年輕代特色是區域相對老年代較小,對像存活率低。這種狀況複製算法的回收整理,速度是最快的。複製算法的效率只和當前存活對像大小有關,於是很適用於年輕代的回收。而複製算法內存利用率不高的問題,經過hotspot中的兩個survivor的設計獲得緩解。
老年代的特色是區域較大,對像存活率高。這種狀況,存在大量存活率高的對像,複製算法明顯變得不合適。通常是由標記清除或者是標記清除與標記整理的混合實現。Mark階段的開銷與存活對像的數量成正比,這點上說來,對於老年代,標記清除或者標記整理有一些不符,但能夠經過多核/線程利用,對併發、並行的形式提標記效率。Sweep階段的開銷與所管理區域的大小形正相關,但Sweep「就地處決」的特色,回收的過程沒有對像的移動。使其相對其它有對像移動步驟的回收算法,仍然是效率最好的。可是須要解決內存碎片問題。Compact階段的開銷與存活對像的數據成開比,如上一條所描述,對於大量對像的移動是很大開銷的,作爲老年代的第一選擇並不合適。基於上面的考慮,老年代通常是由標記清除或者是標記清除與標記整理的混合實現。以hotspot中的CMS回收器爲例,CMS是基於Mark-Sweep實現的,對於對像的回收效率很高,而對於碎片問題,CMS採用基於Mark-Compact算法的Serial Old回收器作爲補償措施:當內存回收不佳(碎片致使的Concurrent Mode Failure時),將採用Serial Old執行Full GC以達到對老年代內存的整理。