JVM垃圾回收

????????垃圾回收(Garbage Collection, GC)誕生于1960年的Lisp語(yǔ)言。在Java虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)中,虛擬機(jī)棧、操作數(shù)棧和程序計(jì)數(shù)器都是隨線程而生,隨線程而亡。當(dāng)方法或者線程結(jié)束時(shí),這部分的內(nèi)存就自然回收。而堆和方法區(qū)這部分內(nèi)存的分配是動(dòng)態(tài)的,因此需要在程序運(yùn)行期間進(jìn)行動(dòng)態(tài)回收。

????????對(duì)于垃圾回收,主要關(guān)注兩點(diǎn)。第一,如何確定一個(gè)對(duì)象是否存活。第二,如何回收。

對(duì)于如何確定對(duì)象是否存活,目前有兩種方法:

1.程序計(jì)數(shù)器

????????給對(duì)象添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有引用指向這個(gè)對(duì)象,計(jì)數(shù)器就加1;當(dāng)引用失效時(shí),計(jì)數(shù)器減1;任何時(shí)刻計(jì)數(shù)器為0的對(duì)象就是不可能再被使用的。雖然這種方法簡(jiǎn)單高效,但是有一個(gè)致命的弱點(diǎn),它很難解決對(duì)象之間互相循環(huán)引用的問(wèn)題。

例子:

2.可達(dá)性分析

????????通過(guò)一系列稱為"GC Root"的對(duì)象作為起點(diǎn),向下檢索,檢索的路徑稱為引用鏈。當(dāng)一個(gè)對(duì)象的GC Root對(duì)象沒有任何引用鏈時(shí),則這個(gè)對(duì)象是不可用的。

GC Root可以是以下幾種。

????????1、虛擬機(jī)棧(棧幀中的局部變量表)中引用的對(duì)象。

????????2、方法區(qū)中靜態(tài)屬性引用的對(duì)象。

????????3、方法區(qū)中常量引用的對(duì)象。

????????4、本地方法棧中JNI(常說(shuō)的Native方法)引用的對(duì)象。

????????可以想象一下,當(dāng)我們一直往房間里堆東西,很快的房間就放不下其他東西了,所有我們會(huì)經(jīng)常對(duì)房間進(jìn)行清理以保證有活動(dòng)的空間。對(duì)于堆來(lái)說(shuō)也是如此,需要定期的清理不需要的對(duì)象保證有足夠的空間可以存放新生成的對(duì)象。對(duì)此JVM提供了4種垃圾回收算法。

1.標(biāo)記清除

????????將可回收的對(duì)象做上標(biāo)記,最后統(tǒng)一回收被標(biāo)記的對(duì)象。這是最簡(jiǎn)單的垃圾回收算法,有2點(diǎn)不足的地方。 一是效率問(wèn)題,標(biāo)記和清除兩個(gè)過(guò)程效率都比較低。二是空間利用率的問(wèn)題,單單標(biāo)記和清除容易產(chǎn)生大量不連續(xù)的內(nèi)存碎片,這將會(huì)導(dǎo)致內(nèi)存無(wú)法分配一整塊大的內(nèi)存而進(jìn)行一次垃圾回收。

2.復(fù)制算法

????????將可用內(nèi)存分為兩塊,每次只使用其中的一塊,當(dāng)這一塊內(nèi)存用完時(shí),將存活的對(duì)象復(fù)制到另一塊去,以此類推。這種回收算法簡(jiǎn)單高效,但是空間利用率很低,每次只能使用一半的內(nèi)存,確實(shí)是有點(diǎn)浪費(fèi)。經(jīng)過(guò)IBM專門研究表明,新生代中98%的對(duì)象都是"朝生夕死"的,所以可以將新生代切割成一塊較大的Eden區(qū)和兩塊較小的Survivor區(qū)。每次使用一塊Eden區(qū)和一塊Survivor區(qū),每次回收將生存的對(duì)象放到另一塊Survivor區(qū),這樣每次就只浪費(fèi)10%的內(nèi)存空間。但生存下來(lái)的對(duì)象不可能每次都小于這10%的內(nèi)存,這時(shí)候就要求助于老年代來(lái)存放這部分對(duì)象。

3.標(biāo)記整理

????????這個(gè)算法相對(duì)于標(biāo)記清除算法稍微高級(jí)一點(diǎn)。它比標(biāo)記清除算法多了一步讓存活的對(duì)象向一邊移動(dòng)的過(guò)程,最后只清理邊界以外被標(biāo)記的對(duì)象。

4.分代收集

????????這個(gè)算法就相當(dāng)于以上幾個(gè)方法在不同堆區(qū)域中的綜合適用。因?yàn)樾律看位厥罩粫?huì)存活少量對(duì)象所以適用復(fù)制算法,而老年代中的對(duì)象通常生存周期長(zhǎng)而且對(duì)象占用的空間也比較大,沒有多余的空間對(duì)這些對(duì)象進(jìn)行空間擔(dān)保,因此多使用標(biāo)記算法。

????????垃圾回收發(fā)生在堆中,而堆內(nèi)存一般分為新生代,老年代和永久帶。再細(xì)致一點(diǎn),新生代可以分為一個(gè)Eden區(qū),F(xiàn)rom Survivor區(qū)和To Survivor區(qū)。被創(chuàng)建的對(duì)象優(yōu)先在Eden區(qū)分配,當(dāng)Eden區(qū)內(nèi)存不足時(shí),觸發(fā)一次Minor GC。因?yàn)镴ava對(duì)象大多數(shù)生命周期較短,所以Eden區(qū)都使用復(fù)制算法來(lái)回收對(duì)象。當(dāng)需要大量的連續(xù)內(nèi)存來(lái)存放Java大對(duì)象時(shí)(長(zhǎng)字符串或數(shù)組),直接進(jìn)入老年代。虛擬機(jī)提供-XX:PretenureSizeThreshold參數(shù)設(shè)置,如果對(duì)象的空間需求大于該參數(shù)則直接進(jìn)入老年代。想起有部分生命力頑強(qiáng)的對(duì)象一直在Survivor區(qū)來(lái)來(lái)去去,經(jīng)歷了這么多次打拼,這些對(duì)象也應(yīng)該告老還鄉(xiāng)了。虛擬機(jī)為每一個(gè)對(duì)象定義了一個(gè)年齡計(jì)數(shù)器,對(duì)象每經(jīng)過(guò)一次Minor GC,年齡計(jì)數(shù)器+1。當(dāng)年齡達(dá)到指定值時(shí)(默認(rèn)是15,可以通過(guò)-XX:MaxTenuringThreshold設(shè)置),對(duì)象就晉升到老年代。

????????在新生代對(duì)象晉升之前,虛擬機(jī)會(huì)先去檢查老年代最大可用的連續(xù)內(nèi)存是否大于晉升對(duì)象所需要的內(nèi)存空間大小,如果成立則這次的Minor GC是安全的。反之虛擬機(jī)會(huì)查看HandlePromotionFailure設(shè)置值是否允許擔(dān)保失敗。如果允許,那么會(huì)繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于這次晉升對(duì)象的平均大小,如果大于則進(jìn)行一次Minor GC,如果小于或者HandlePromotionFailure設(shè)置不允許冒險(xiǎn),這時(shí)就要進(jìn)行一個(gè)Full GC。

????????上面講了很多關(guān)于回收的算法,它只是垃圾回收的方法論。接下來(lái)講述垃圾回收關(guān)注的第二點(diǎn),如何回收。垃圾回收其實(shí)主要是通過(guò)各種收集器來(lái)進(jìn)行的。

Serial收集器

????????串行收集器是最古老,最穩(wěn)定以及效率高的收集器。它是一個(gè)單線程收集器,單線程的意思是它工作的時(shí)候只能它一個(gè)線程在工作。好比他吃飯的時(shí)候別人必須只能看著他吃。新生代采用復(fù)制算法,老年代采用標(biāo)記整理算法,工作時(shí)暫停其他所有線程。

????????-XX:+UseSerialGC 串行收集器。

ParNew

????????它是Serial收集器的多線程版。新生代并行執(zhí)行,老年代并發(fā)執(zhí)行。

????????并行:多條垃圾收集線程并行工作,但用戶線程仍處于等待狀態(tài)。

????????并發(fā):用戶線程和垃圾回收線程同時(shí)工作。

????????-XX:+UseParNewGC ParNew收集器。

????????-XX:ParallelGCThreads 限制線程數(shù)量。

Parallel Scavenge

????????Parallel Scavenge 與ParNew收集器類似,它更關(guān)注系統(tǒng)的吞吐量。它可以通過(guò)調(diào)節(jié)執(zhí)行GC自 適應(yīng)調(diào)節(jié)策略來(lái)達(dá)到最大吞吐量的目的。

????????-XX:+UseParallelGC 使用Parallel收集器+老年代串行。

CMS(Concurrent Mark Sweep)

????????它是一種以獲取最短回收停頓為目標(biāo)的收集器。常用于互聯(lián)網(wǎng)或B/S此類重視響應(yīng)速度的服務(wù)端。

????????CMS的清除過(guò)程相對(duì)于前面幾種更為復(fù)雜。

????????1.初始標(biāo)記 stop-the-world 僅標(biāo)記GC Roots能直接關(guān)聯(lián)到的對(duì)象。

????????2.并發(fā)標(biāo)記 stop-the-world 進(jìn)行GC Roots Tracing的過(guò)程 停頓時(shí)間長(zhǎng)。

????????3.重新標(biāo)記 修正并發(fā)標(biāo)記期因用戶程序繼續(xù)運(yùn)行導(dǎo)致標(biāo)記變動(dòng)的部分。

????????4.并發(fā)清除 與工作線程一起工作。

????????-XX:+UseConcMarkSweepGC?使用CMS收集器。

????????-XX:+ UseCMSCompactAtFullCollection?Full GC后,進(jìn)行一次碎片整理;整理過(guò)程是獨(dú)占的,會(huì)引起停頓時(shí)間變長(zhǎng)。

????????-XX:+CMSFullGCsBeforeCompaction?設(shè)置進(jìn)行幾次Full GC后,進(jìn)行一次碎片整理。

????????-XX:ParallelCMSThreads?設(shè)定CMS的線程數(shù)量(一般情況約等于可用CPU數(shù)量)。

????????優(yōu)點(diǎn):并發(fā)收集,停頓時(shí)間短。

????????缺點(diǎn):使用標(biāo)記清除算法產(chǎn)生大量不連續(xù)空間碎片,并發(fā)階段降低吞吐量。

G1

????????它是當(dāng)前垃圾收集器最前沿的成果之一,目標(biāo)是在未來(lái)能替換掉CMS收集器。

????????優(yōu)勢(shì):

????????1.并發(fā)與并行 縮短stop-the-world停頓時(shí)間。

????????2.分代收集 獨(dú)立管理整個(gè)GC堆。

????????3.空間整理 采用標(biāo)記整理算法不會(huì)產(chǎn)生不連續(xù)空間碎片。

????????4.可預(yù)測(cè)停頓 在追求低停頓的同時(shí)還能建立可預(yù)測(cè)的停頓時(shí)間模型,使消耗在垃圾收集的時(shí)間上不超過(guò)N毫秒。

????????清除過(guò)程:

????????1.初始標(biāo)記 stop-the-world 觸發(fā)Minor GC。

????????2.根區(qū)域掃描 回收survivor區(qū)域,這過(guò)程必須在minor GC之前完成。

????????3.并發(fā)標(biāo)記 在整個(gè)堆中并發(fā)標(biāo)記,并計(jì)算每個(gè)區(qū)域中存活對(duì)象的比例。

????????4.再標(biāo)記 用于收集并發(fā)標(biāo)記階段產(chǎn)生的新垃圾。

????????5.復(fù)制清除 多線程清除對(duì)象,stop-the-world G1將回收區(qū)域的存活對(duì)象拷貝到新區(qū)域,清除Remember Sets,并發(fā)清空回收區(qū)并把它返回到空閑區(qū)域鏈表中。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容