JAVA垃圾回收器的介绍
JAVA垃圾回收器的介紹
垃圾回收器一共有7種:
如下圖
其中G1 和 CMS 屬于比較新的,暫停時(shí)間比之前較少。
serial
serial Old
parNew 這個(gè)是serial的多線程版本。
Parallel Scavenge
parallel old 這個(gè)是Parallel Scavenge的老年代版本。
CMS(concurrent mark sweep)
G1(Gabage first)
CMS回收器和G1回收器的對(duì)比
1 CMS回收器
并行回收器之后就是CMS回收器了(concurrent-mark-sweep)。這個(gè)算法使用了多個(gè)線程(concurrent)來(lái)掃描堆并標(biāo)記(mark)那些不再使用的可以回收(sweep)的對(duì)象。這個(gè)算法在兩種情況下會(huì)進(jìn)入一個(gè)”stop the world”的模式:當(dāng)進(jìn)行根對(duì)象的初始標(biāo)記的時(shí)候 (老生代中線程入口點(diǎn)或靜態(tài)變量可達(dá)的那些對(duì)象)以及當(dāng)這個(gè)算法在并發(fā)運(yùn)行的時(shí)候應(yīng)用程序改變了堆的狀態(tài)使得它不得不回去再次確認(rèn)自己標(biāo)記的對(duì)象都是正確的。
使用這個(gè)回收器最大的問(wèn)題就是會(huì)碰到promotion failure,這是指在回收新生代及年老代時(shí)出現(xiàn)了競(jìng)爭(zhēng)條件的情況。如果回收器需要將年輕的對(duì)象提升到年老代中,而這個(gè)時(shí)候年老代沒(méi)有多余的空間了,它就只能先進(jìn)行一次STW(Stop The World)的full GC了——這種情況正是CMS所希望避免的。為了確保這種情況不會(huì)發(fā)生,你要么就是增加老生代的大小(或者增加整個(gè)堆的大小),要么就是給回收器分配一些后臺(tái)線程以便與對(duì)象分配的速度進(jìn)行賽跑。
這個(gè)算法的另一個(gè)缺點(diǎn)就是和并行回收器相比,它使用的CPU資源會(huì)更多,它使用了多個(gè)線程來(lái)執(zhí)行掃描和回收,這樣才能讓?xiě)?yīng)用持續(xù)提供更高級(jí)別的吞吐量。對(duì)于大多數(shù)長(zhǎng)期運(yùn)行的程序而言,應(yīng)用的暫停對(duì)它們是很不利的,這個(gè)時(shí)候可以考慮使用CMS回收器。盡管如此,這個(gè)算法也不是默認(rèn)開(kāi)啟的。你得指定XX:+UseConcMarkSweepGC來(lái)啟用它。假設(shè)你的堆小于4G,而你又希望分配更多的CPU資源以避免應(yīng)用暫停,那么這就是你要選擇的回收器。然而,如果堆大于4G的話,你可能更希望使用最后的這個(gè)——G1回收器。
2 G1回收器
G1( Garbage first)回收器在JDK 7update 4中首次引入,它的設(shè)計(jì)目標(biāo)是能更好地支持大于4GB的堆。G1回收器將堆分為多個(gè)區(qū)域,大小從1MB到32MB不等,并使用多個(gè)后臺(tái)線程來(lái)掃描它們。G1回收器會(huì)優(yōu)先掃描那些包含垃圾最多的區(qū)域,這正是它的名字的由來(lái)(Garbage first)。這個(gè)回收器可以通過(guò)-XX:UseG1GC標(biāo)記來(lái)啟用。
這一策略減少了后臺(tái)線程還未掃描完無(wú)用對(duì)象前堆就已經(jīng)用光的可能性,而那種情況回收器就必須得暫停應(yīng)用,這就會(huì)導(dǎo)致STW回收。G1的另一個(gè)好處就是它總是會(huì)進(jìn)行堆的壓縮,而CMS回收器只有在full GC的時(shí)候才會(huì)干這事。
過(guò)去幾年里,大堆一直都是一個(gè)充滿爭(zhēng)議的領(lǐng)域,很多開(kāi)發(fā)人員從單機(jī)器單JVM模型轉(zhuǎn)向了單機(jī)器多JVM的微服務(wù),組件化的架構(gòu)。這是許多因素所驅(qū)動(dòng)的,包括隔離程序的組件,簡(jiǎn)化部署,避免重新加載應(yīng)用類到內(nèi)存所產(chǎn)生的開(kāi)銷(Java 8中這點(diǎn)已經(jīng)得到了改善)。
盡管如此,這么做最主要還是希望能避免大堆的GC中長(zhǎng)時(shí)期的”stop the world”的暫停(在一次大的回收中需要花費(fèi)數(shù)秒才能完成)。像Docker這樣的容器技術(shù)也加速了這一進(jìn)程,它們使得你可以很輕松地在同一臺(tái)物理機(jī)上部署多個(gè)應(yīng)用。
Serial
Serial收集器是最古老的收集器,它的缺點(diǎn)是當(dāng)Serial收集器想進(jìn)行垃圾回收的時(shí)候,必須暫停用戶的所有進(jìn)程,即stop the world。到現(xiàn)在為止,它依然是虛擬機(jī)運(yùn)行在client模式下的默認(rèn)新生代收集器,與其他收集器相比,對(duì)于限定在單個(gè)CPU的運(yùn)行環(huán)境來(lái)說(shuō),Serial收集器由于沒(méi)有線程交互的開(kāi)銷,專心做垃圾回收自然可以獲得最高的單線程收集效率。
Serial Old
是Serial收集器的老年代版本,它同樣是一個(gè)單線程收集器,使用”標(biāo)記-整理“算法。這個(gè)收集器的主要意義也是被Client模式下的虛擬機(jī)使用。在Server模式下,它主要還有兩大用途:一個(gè)是在JDK1.5及以前的版本中與Parallel Scanvenge收集器搭配使用,另外一個(gè)就是作為CMS收集器的后備預(yù)案,在并發(fā)收集發(fā)生Concurrent Mode Failure的時(shí)候使用。
通過(guò)指定-UseSerialGC參數(shù),使用Serial + Serial Old的串行收集器組合進(jìn)行內(nèi)存回收。
ParNew收集器
ParNew收集器是Serial收集器新生代的多線程實(shí)現(xiàn),注意在進(jìn)行垃圾回收的時(shí)候依然會(huì)stop the world,只是相比較Serial收集器而言它會(huì)運(yùn)行多條進(jìn)程進(jìn)行垃圾回收。
ParNew收集器在單CPU的環(huán)境中絕對(duì)不會(huì)有比Serial收集器更好的效果,甚至由于存在線程交互的開(kāi)銷,該收集器在通過(guò)超線程技術(shù)實(shí)現(xiàn)的兩個(gè)CPU的環(huán)境中都不能百分之百的保證能超越Serial收集器。當(dāng)然,隨著可以使用的CPU的數(shù)量增加,它對(duì)于GC時(shí)系統(tǒng)資源的利用還是很有好處的。它默認(rèn)開(kāi)啟的收集線程數(shù)與CPU的數(shù)量相同,在CPU非常多(譬如32個(gè),現(xiàn)在CPU動(dòng)輒4核加超線程,服務(wù)器超過(guò)32個(gè)邏輯CPU的情況越來(lái)越多了)的環(huán)境下,可以使用-XX:ParallelGCThreads參數(shù)來(lái)限制垃圾收集的線程數(shù)。
-UseParNewGC: 打開(kāi)此開(kāi)關(guān)后,使用ParNew + Serial Old的收集器組合進(jìn)行內(nèi)存回收,這樣新生代使用并行收集器,老年代使用串行收集器。
Parallel Scavenge收集器
Parallel是采用復(fù)制算法的多線程新生代垃圾回收器,似乎和ParNew收集器有很多的相似的地方。但是Parallel Scanvenge收集器的一個(gè)特點(diǎn)是它所關(guān)注的目標(biāo)是吞吐量(Throughput)。所謂吞吐量就是CPU用于運(yùn)行用戶代碼的時(shí)間與CPU總消耗時(shí)間的比值,即吞吐量=運(yùn)行用戶代碼時(shí)間 / (運(yùn)行用戶代碼時(shí)間 + 垃圾收集時(shí)間)。停頓時(shí)間越短就越適合需要與用戶交互的程序,良好的響應(yīng)速度能夠提升用戶的體驗(yàn);而高吞吐量則可以最高效率地利用CPU時(shí)間,盡快地完成程序的運(yùn)算任務(wù)。
主要適合在后臺(tái)運(yùn)算而不需要太多交互的任務(wù)。
Parallel Old收集器是
Parallel Scavenge收集器的老年代版本,采用多線程和”標(biāo)記-整理”算法。這個(gè)收集器是在jdk1.6中才開(kāi)始提供的,在此之前,新生代的Parallel Scavenge收集器一直處于比較尷尬的狀態(tài)。原因是如果新生代Parallel Scavenge收集器,那么老年代除了Serial Old(PS MarkSweep)收集器外別無(wú)選擇。由于單線程的老年代Serial Old收集器在服務(wù)端應(yīng)用性能上的”拖累“,即使使用了Parallel Scavenge收集器也未必能在整體應(yīng)用上獲得吞吐量最大化的效果,又因?yàn)槔夏甏占袩o(wú)法充分利用服務(wù)器多CPU的處理能力,在老年代很大而且硬件比較高級(jí)的環(huán)境中,這種組合的吞吐量甚至還不一定有ParNew加CMS的組合”給力“。直到Parallel Old收集器出現(xiàn)后,”吞吐量?jī)?yōu)先“收集器終于有了比較名副其實(shí)的應(yīng)用祝賀,在注重吞吐量及CPU資源敏感的場(chǎng)合,都可以優(yōu)先考慮Parallel Scavenge加Parallel Old收集器。
-UseParallelGC: 虛擬機(jī)運(yùn)行在Server模式下的默認(rèn)值,打開(kāi)此開(kāi)關(guān)后,使用Parallel Scavenge + Serial Old的收集器組合進(jìn)行內(nèi)存回收。-UseParallelOldGC: 打開(kāi)此開(kāi)關(guān)后,使用Parallel Scavenge + Parallel Old的收集器組合進(jìn)行垃圾回收
CMS收集器
CMS(Concurrent Mark Swep)收集器是一個(gè)比較重要的回收器,現(xiàn)在應(yīng)用非常廣泛,我們重點(diǎn)來(lái)看一下,CMS一種獲取最短回收停頓時(shí)間為目標(biāo)的收集器,這使得它很適合用于和用戶交互的業(yè)務(wù)。從名字(Mark Swep)就可以看出,CMS收集器是基于標(biāo)記清除算法實(shí)現(xiàn)的。它的收集過(guò)程分為四個(gè)步驟:
初始標(biāo)記(initial mark)
并發(fā)標(biāo)記(concurrent mark)
重新標(biāo)記(remark)
并發(fā)清除(concurrent sweep)
注意初始標(biāo)記和重新標(biāo)記還是會(huì)stop the world,但是在耗費(fèi)時(shí)間更長(zhǎng)的并發(fā)標(biāo)記和并發(fā)清除兩個(gè)階段都可以和用戶進(jìn)程同時(shí)工作。
不過(guò)由于CMS收集器是基于標(biāo)記清除算法實(shí)現(xiàn)的,會(huì)導(dǎo)致有大量的空間碎片產(chǎn)生,在為大對(duì)象分配內(nèi)存的時(shí)候,往往會(huì)出現(xiàn)老年代還有很大的空間剩余,但是無(wú)法找到足夠大的連續(xù)空間來(lái)分配當(dāng)前對(duì)象,不得不提前開(kāi)啟一次Full GC。為了解決這個(gè)問(wèn)題,CMS收集器默認(rèn)提供了一個(gè)-XX:+UseCMSCompactAtFullCollection收集開(kāi)關(guān)參數(shù)(默認(rèn)就是開(kāi)啟的),用于在CMS收集器進(jìn)行FullGC完開(kāi)啟內(nèi)存碎片的合并整理過(guò)程,內(nèi)存整理的過(guò)程是無(wú)法并發(fā)的,這樣內(nèi)存碎片問(wèn)題倒是沒(méi)有了,不過(guò)停頓時(shí)間不得不變長(zhǎng)。虛擬機(jī)設(shè)計(jì)者還提供了另外一個(gè)參數(shù)-XX:CMSFullGCsBeforeCompaction參數(shù)用于設(shè)置執(zhí)行多少次不壓縮的FULL GC后跟著來(lái)一次帶壓縮的(默認(rèn)值為0,表示每次進(jìn)入Full GC時(shí)都進(jìn)行碎片整理)。
不幸的是,它作為老年代的收集器,卻無(wú)法與jdk1.4中已經(jīng)存在的新生代收集器Parallel Scavenge配合工作,所以在jdk1.5中使用cms來(lái)收集老年代的時(shí)候,新生代只能選擇ParNew或Serial收集器中的一個(gè)。ParNew收集器是使用-XX:+UseConcMarkSweepGC選項(xiàng)啟用CMS收集器之后的默認(rèn)新生代收集器,也可以使用-XX:+UseParNewGC選項(xiàng)來(lái)強(qiáng)制指定它。
由于CMS收集器現(xiàn)在比較常用,下面我們?cè)兕~外了解一下CMS算法的幾個(gè)常用參數(shù):
UseCMSInitatingOccupancyOnly:表示只在到達(dá)閾值的時(shí)候,才進(jìn)行 CMS 回收。
為了減少第二次暫停的時(shí)間,通過(guò)-XX:+CMSParallelRemarkEnabled開(kāi)啟并行remark。如果ramark時(shí)間還是過(guò)長(zhǎng)的話,可以開(kāi)啟-XX:+CMSScavengeBeforeRemark選項(xiàng),強(qiáng)制remark之前開(kāi)啟一次minor gc,減少remark的暫停時(shí)間,但是在remark之后也立即開(kāi)始一次minor gc。
CMS默認(rèn)啟動(dòng)的回收線程數(shù)目是(ParallelGCThreads + 3)/4,如果你需要明確設(shè)定,可以通過(guò)-XX:+ParallelCMSThreads來(lái)設(shè)定,其中-XX:+ParallelGCThreads代表的年輕代的并發(fā)收集線程數(shù)目。
CMSClassUnloadingEnabled: 允許對(duì)類元數(shù)據(jù)進(jìn)行回收。
CMSInitatingPermOccupancyFraction:當(dāng)永久區(qū)占用率達(dá)到這一百分比后,啟動(dòng) CMS 回收 (前提是-XX:+CMSClassUnloadingEnabled 激活了)。
CMSIncrementalMode:使用增量模式,比較適合單 CPU。
UseCMSCompactAtFullCollection參數(shù)可以使 CMS 在垃圾收集完成后,進(jìn)行一次內(nèi)存碎片整理。內(nèi)存碎片的整理并不是并發(fā)進(jìn)行的。
UseFullGCsBeforeCompaction:設(shè)定進(jìn)行多少次 CMS 垃圾回收后,進(jìn)行一次內(nèi)存壓縮。
一些建議
對(duì)于Native Memory:
使用了NIO或者NIO框架(Mina/Netty)
使用了DirectByteBuffer分配字節(jié)緩沖區(qū)
使用了MappedByteBuffer做內(nèi)存映射
由于Native Memory只能通過(guò)FullGC回收,所以除非你非常清楚這時(shí)真的有必要,否則不要輕易調(diào)用System.gc()。
另外為了防止某些框架中的System.gc調(diào)用(例如NIO框架、Java RMI),建議在啟動(dòng)參數(shù)中加上-XX:+DisableExplicitGC來(lái)禁用顯式GC。這個(gè)參數(shù)有個(gè)巨大的坑,如果你禁用了System.gc(),那么上面的3種場(chǎng)景下的內(nèi)存就無(wú)法回收,可能造成OOM,如果你使用了CMS GC,那么可以用這個(gè)參數(shù)替代:-XX:+ExplicitGCInvokesConcurrent。
此外除了CMS的GC,其實(shí)其他針對(duì)old gen的回收器都會(huì)在對(duì)old gen回收的同時(shí)回收young gen。
G1收集器
G1收集器是一款面向服務(wù)端應(yīng)用的垃圾收集器。HotSpot團(tuán)隊(duì)賦予它的使命是在未來(lái)替換掉JDK1.5中發(fā)布的CMS收集器。與其他GC收集器相比,G1具備如下特點(diǎn):
并行與并發(fā):G1能更充分的利用CPU,多核環(huán)境下的硬件優(yōu)勢(shì)來(lái)縮短stop the world的停頓時(shí)間。
分代收集:和其他收集器一樣,分代的概念在G1中依然存在,不過(guò)G1不需要其他的垃圾回收器的配合就可以獨(dú)自管理整個(gè)GC堆。
空間整合:G1收集器有利于程序長(zhǎng)時(shí)間運(yùn)行,分配大對(duì)象時(shí)不會(huì)無(wú)法得到連續(xù)的空間而提前觸發(fā)一次GC。
可預(yù)測(cè)的非停頓:這是G1相對(duì)于CMS的另一大優(yōu)勢(shì),降低停頓時(shí)間是G1和CMS共同的關(guān)注點(diǎn),能讓使用者明確指定在一個(gè)長(zhǎng)度為M毫秒的時(shí)間片段內(nèi),消耗在垃圾收集上的時(shí)間不得超過(guò)N毫秒。
在使用G1收集器時(shí),Java堆的內(nèi)存布局和其他收集器有很大的差別,它將這個(gè)Java堆分為多個(gè)大小相等的獨(dú)立區(qū)域,雖然還保留新生代和老年代的概念,但是新生代和老年代不再是物理隔離的了,它們都是一部分Region(不需要連續(xù))的集合。
雖然G1看起來(lái)有很多優(yōu)點(diǎn),實(shí)際上CMS還是主流。
總結(jié)
以上是生活随笔為你收集整理的JAVA垃圾回收器的介绍的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。