本文章內(nèi)容基于《深入了解Java虛擬機(jī)》第二版,對(duì)里面知識(shí)點(diǎn)進(jìn)行總結(jié),JDK主要是1.6和1.7。
以下內(nèi)容圍繞HotSpot虛擬機(jī)的各種垃圾收集起實(shí)現(xiàn)。
目前沒有一個(gè)最好的收集器,都是針對(duì)不同區(qū)域的特性實(shí)現(xiàn)最好垃圾收集方法

Serial收集器
單線程的收集器(JDK1.3之前新生代收集器唯一選擇)
注意:單線程不是表示使用單個(gè)CPU或一個(gè)收集線程,而是進(jìn)行垃圾收集,必須暫停其他所有工作線程

后續(xù)JDK不斷更新,目前只能不斷縮短暫停時(shí)間,無法完全消除(排除RTSJ的收集器)
應(yīng)用范圍:目前應(yīng)用虛擬機(jī)運(yùn)行在Client模式下的新生代收集器。
優(yōu)點(diǎn):簡單而高效(與其他收集器的單線程對(duì)比)
原因:
1、單CPU環(huán)境,Serial收集器沒有線程交互的開銷
2、用戶的桌面應(yīng)用場景,虛擬機(jī)管的內(nèi)容一般不大,暫停時(shí)間可以控制到接受范圍。
ParNew收集器
ParNew為Serial的多線程版本,其他與Serial完全一樣

應(yīng)用范圍:運(yùn)行于Server模式的虛擬機(jī)首先新生代收集器
原因:
1、只有Serial與PerNew能與CMS收集配合工作(CMS收集器是優(yōu)秀并發(fā)收集器,實(shí)現(xiàn)垃圾線程和用戶線程基本上同時(shí)工作;CMS作為老年代的收集器)
Parallel Scavenge收集器
該收集器是新生代收集器,使用復(fù)制算法,且是多線程
特點(diǎn):其他收集器都為縮短暫停用戶線程的時(shí)間,該收集器目標(biāo)則是達(dá)到一個(gè)可控的吞吐量。
吞吐量 = 運(yùn)行用戶代碼時(shí)間 / ( 運(yùn)行用戶代碼時(shí)間 + 垃圾收集時(shí)間)
對(duì)比:
1、暫停時(shí)間越短:適合需要與用戶交互的程序,提交響應(yīng)速度。
2、吞吐量高:高效率使用CPU,盡快完成程序的運(yùn)算任務(wù),適合后臺(tái)運(yùn)算不需要太多交互的任務(wù)。
注意:GC暫停時(shí)間縮短一般通過犧牲吞吐量和空間來實(shí)現(xiàn)。
Serial Old收集器
Serial收集器的老年代版本,單線程收集器,使用標(biāo)記-整理算法,Client模式下使用。
用途:
1、與Parallel Scavenge收集器搭配使用
2、作為CMS收集器的后備預(yù)案。
Parallel Old收集器
是Parallel Scavenge收集器的老年代版本,多線程、標(biāo)記-整理算法。
用途:
Parallel Scavenge收集器無法與CMS收集器配合,Serial Old單線程在服務(wù)端性能不足。推出Parallel Old收集器配合Parallel Scavenge收集器在服務(wù)端提性能。

CMS收集器
是一種一獲取最短回收暫停時(shí)間為目標(biāo)的收集器,目前廣泛應(yīng)用互聯(lián)網(wǎng)站或B/S系統(tǒng)的服務(wù)端?;跇?biāo)記-清除算法
分為四個(gè)階段:
1、初始標(biāo)記:標(biāo)記GC Roots能直接關(guān)聯(lián)的對(duì)象;暫停用戶線程
2、并發(fā)標(biāo)記:進(jìn)行GC Roots Tracing過程
3、重新標(biāo)記:修正并發(fā)標(biāo)記期間因用戶程序運(yùn)行導(dǎo)致標(biāo)記變動(dòng);暫停用戶線程
4、并發(fā)清除:多線程清除無用對(duì)象。

缺點(diǎn):
1、CMS收集器對(duì)CPU資源非常敏感,占用一部分CPU資源,導(dǎo)致應(yīng)用程序變慢,總吞吐量降低。(CPU數(shù)量越少,CMS性能變低)
2、CMS收集器無法處理浮動(dòng)垃圾。并發(fā)清除過程中,用戶線程還會(huì)生產(chǎn)新的垃圾,需要等到下次GC才能清除。
而且運(yùn)行過程還要給用戶線程預(yù)留內(nèi)存空間,當(dāng)出現(xiàn)預(yù)留的空間無法滿足程序需求,則要臨時(shí)啟動(dòng)Serial Old收集器邏輯,
3、采用標(biāo)記-清除算法會(huì)出現(xiàn)很多碎片,不足是會(huì)進(jìn)行 full GC操作
G1收集器
面對(duì)服務(wù)端應(yīng)用的垃圾收集器。替換CMS收集器
特點(diǎn):
1、并行與并發(fā):充分利用多CPU、多核環(huán)境的優(yōu)勢
2、分代收集:G1不需要與其他收集器配合,獨(dú)立管理整個(gè)GC堆
3、空間整合:整體為標(biāo)記整理算法,局部為復(fù)制算法(兩個(gè)region之間)
4、可預(yù)測的停頓:建立可預(yù)測的停頓時(shí)間模型,能夠讓使用者明確指定的一個(gè)長度為M毫秒的時(shí)間片段內(nèi)。垃圾收集回收不超過N毫秒。
內(nèi)存分配:
G1收集起將整個(gè)Java堆分為多個(gè)大小相等的獨(dú)立區(qū)域(新生代和老生代不在物理隔離,是一部分region的集合)
建立可預(yù)測的停頓時(shí)間模型:
G1跟蹤各個(gè)region的垃圾堆積的價(jià)值大小(收集獲取空間以及回收所需時(shí)間),后臺(tái)維護(hù)一個(gè)優(yōu)先列表,優(yōu)先回收價(jià)值最大的region。
問題:單獨(dú)region內(nèi)的對(duì)象會(huì)被其他region對(duì)象引用,難道還是要整個(gè)Java堆掃描?
答:使用Remembered Set來避免全表掃描,每個(gè)region都有一個(gè)對(duì)應(yīng)的Remembered Set。為了登記引用對(duì)象被其他region對(duì)象引用的信息。
進(jìn)行內(nèi)存回收時(shí),會(huì)引入Remembered Set保證不對(duì)全堆掃描。
如果不包含維護(hù)Remembered Set,步驟為
1、初始標(biāo)記:標(biāo)記GC Roots能直接關(guān)聯(lián)的對(duì)象;暫停用戶線程
2、并發(fā)標(biāo)記:進(jìn)行GC Roots Tracing過程
3、最終標(biāo)記:修正并發(fā)標(biāo)記期間因用戶程序運(yùn)行導(dǎo)致標(biāo)記變動(dòng);暫停用戶線程
該階段會(huì)維護(hù)Remembered Set
4、篩選回收:多線程清除無用對(duì)象。

內(nèi)存分配與回收策略總結(jié)
對(duì)象內(nèi)存分配的大方向就是堆上分配。
獨(dú)享主要分配在新生代的Eden區(qū)。如果啟動(dòng)本地線程分配緩沖,將按照線程優(yōu)先在TLAB上分配。少數(shù)情況也可能直接分配在老年代。
對(duì)象優(yōu)先分配在Eden分配
大多數(shù)情況之下,對(duì)象在新生代Eden區(qū)中分配,當(dāng)Eden區(qū)沒有足夠空間,虛擬機(jī)會(huì)進(jìn)行一個(gè)Minor GC。
Minor GC與Full GC區(qū)別
新生代(Minor GC):新生代的垃圾回收,非常頻繁,一般回收速度較快。
老年代(Major GC/Full GC):老年代的垃圾回收,出現(xiàn)Major GC之前一般至少出現(xiàn)一次Minor GC,Minor GC速度一般比Minor GC慢10倍以上。
老年代
大對(duì)象直接進(jìn)入老年代:
大對(duì)象是指需要大量連續(xù)的內(nèi)存空間對(duì)象(長字符串以及數(shù)組)。
長期存活的對(duì)象將進(jìn)入老年代:
每個(gè)對(duì)象都會(huì)定義一個(gè)年齡(Age)計(jì)算器,每次Minor GC之后還存在,則年齡+1。隨著年齡增加到一定程度(默認(rèn)為15)就晉升到老年代。
動(dòng)態(tài)對(duì)象年齡判定:
虛擬機(jī)不是一直要求年齡必須達(dá)到指定年齡才能進(jìn)入老年代。如果Survivor空間中相同年齡所有對(duì)象大小總和大于survivor空間的一半,年齡大于或則等于該年齡的對(duì)象就可以直接進(jìn)入老年代。
空間分配擔(dān)保:
Minor GC之前,虛擬機(jī)會(huì)先檢查老年代最大可用連續(xù)空間是否大于新生代所有對(duì)象總空間,如果條件成立,那么Minor GC可以確保是安全。
如果條件不成立,會(huì)檢查是否允許擔(dān)保失?。?br>
如果允許:繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對(duì)象的平均大小。
如果大于:嘗試進(jìn)行一次Minor GC(該GC存在風(fēng)險(xiǎn))
如果小于或則不允許冒險(xiǎn):則進(jìn)行一次Full GC
何為冒險(xiǎn)?
新生代使用復(fù)制算法,其中一個(gè)survivor空間作為輪換備份,如果出現(xiàn)大量對(duì)象在Minor GC之后存在,則需要老年代進(jìn)行分配擔(dān)保,把survivor無法容納的對(duì)象直接進(jìn)入老年代。冒險(xiǎn)在于老年代剩余空間是否足夠。