1.概述
在我們開(kāi)展關(guān)于HotSpot虛擬機(jī)收集器討論之前,我們來(lái)簡(jiǎn)單回顧一下JVM堆和方法區(qū)內(nèi)存區(qū)域的劃分與管理以及針對(duì)不同區(qū)域所采用的垃圾回收算法。

從上圖我們可以看出,堆內(nèi)存管理采用分代管理最為合適,Why?因?yàn)椴煌瑢?duì)象的生命周期不同,而且98%的對(duì)象都是新生代中的臨時(shí)對(duì)象。而且,根據(jù)各代的特點(diǎn)應(yīng)用不同的GC算法,提高GC效率。
如果說(shuō)收集算法是內(nèi)存回收的方法論,那么垃圾收集器就是內(nèi)存回收的具體實(shí)現(xiàn)。這里討論的收集器基于JDK1.7-Update14之后的HotSpot虛擬機(jī),其包含的所有收集器如下圖所示。

從上圖中,我們看不到針對(duì)永久代的垃圾回收器,那么永久代的垃圾該如何處理呢?當(dāng)永久代和老年代觸發(fā)GC時(shí),除CMS外均會(huì)觸發(fā)Full GC。首先按照新生代配置的GC方式進(jìn)行Minor GC,再按照老年代配置的GC方式對(duì)老年代和永久代進(jìn)行GC。若JVM估計(jì)Minor GC后可能會(huì)發(fā)生晉升失敗,則直接采用老年代配置的GC方式對(duì)新生代、老年代和永久代進(jìn)行Full GC。
2.新生代可用GC
新生代可用的垃圾收集器有:Serial收集器、ParNew收集器、Parallel Scavenge收集器和G1收集器。其中,Serial收集器、ParNew收集器和Parallel Scavenge收集器三者的共同點(diǎn)如下:
- 均使用復(fù)制算法,原理上是一致的:
a.拷貝Eden和From中存活對(duì)象到To中;
b.部分對(duì)象由于某些原因晉升到Old中;
c.清空Eden、From,F(xiàn)rom和To交換身份直到下一次GC發(fā)生。 - 分配對(duì)象內(nèi)存時(shí),Eden空間不足時(shí)觸發(fā)GC。
2.1.Serial收集器(串行收集器)
Serial收集器是最基本、發(fā)展歷史最悠久的收集器,它是一個(gè)單線程的收集器,但它的“單線程”的意義并不僅僅說(shuō)明它只會(huì)使用一個(gè)CPU或一條收集線程去完成垃圾收集工作,更重要的是在它進(jìn)行垃圾收集時(shí),必須暫停其他所有的工作線程(Stop The World),直到它收集結(jié)束。

- 特性:Serial(串行)、Stop The World(暫停所有的工作線程)
- 適用場(chǎng)景:?jiǎn)蜟PU、新生代小、對(duì)暫停時(shí)間要求不高的應(yīng)用,如Client模式的應(yīng)用
- 對(duì)象直接分配在Old(老年代)的情況:對(duì)象大小超過(guò)Eden空間大小和大對(duì)象
- 對(duì)象晉升規(guī)則:經(jīng)歷多次MinorGC仍存活的對(duì)象;To空間不足對(duì)象直接晉升
2.2.ParNew收集器(并行收集器)
ParNew收集器其實(shí)就是Serial收集器的多線程版本,除了使用多條線程進(jìn)行垃圾收集之外,其余行為包括Serial收集器可用的所有控制參數(shù)(例如:-XX:SurvivorRatio、 -XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、對(duì)象分配規(guī)則、回收策略等都與Serial收集器完全一樣。

- 特性:Parallel(并行)、Stop The World(暫停所有的工作線程)
- 適用場(chǎng)景:多CPU、Server模式
- 可以搭配Serial Old和CMS垃圾收集器,不可搭配Parallel Old垃圾收集器
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)資源的有效利用還是很有好處的。ParNew收集器默認(rèn)開(kāi)啟的GC線程數(shù)與CPU的數(shù)量相同,可以使用-XX:ParallelGCThreads參數(shù)來(lái)限制垃圾收集的線程數(shù)。
2.3.Parallel Scavenge收集器(并行清除收集器)
Parallel Scavenge收集器是一個(gè)新生代收集器,它也是使用復(fù)制算法的收集器,又是并行的多線程收集器······看上去和ParNew都一樣,那它有什么特別之處呢?
Parallel Scavenge收集器的特點(diǎn)是它的關(guān)注點(diǎn)與其他收集器不同,CMS等收集器的關(guān)注點(diǎn)是盡可能地縮短垃圾收集時(shí)用戶線程的停頓時(shí)間,而Parallel Scavenge收集器的目的則是達(dá)到一個(gè)可控制的吞吐量(Throughput),所以也稱為“吞吐量?jī)?yōu)先”收集器。

Parallel Scavenge收集器提供了兩個(gè)參數(shù)用于精確控制吞吐量,分別是控制最大垃圾收集停頓時(shí)間的-XX:MaxGCPauseMillis參數(shù)以及直接設(shè)置吞吐量大小的-XX:GCTimeRatio參數(shù)。Parallel Scavenge收集器還有一個(gè)參數(shù)-XX:+useAdaptiveSizePolicy值得關(guān)注。這是一個(gè)開(kāi)關(guān)參數(shù),當(dāng)這個(gè)參數(shù)打開(kāi)之后,就不需要手工指定新生代的大?。?Xmn)、Eden和Survivor去的比例(-XX:SurvivorRatio)、晉升老年代對(duì)象年齡(-XX:PretenureSizeThreshold)等細(xì)節(jié)參數(shù)了,虛擬機(jī)會(huì)根據(jù)當(dāng)前系統(tǒng)的運(yùn)行情況收集性能監(jiān)控信息,動(dòng)態(tài)調(diào)整這些參數(shù)以提供最合適的停頓時(shí)間或者最大吞吐量,這種調(diào)節(jié)方式稱為GC自適應(yīng)的調(diào)節(jié)策略。
- 特性
a.Parallel(并行)、Stop The World(暫停所有的工作線程)
b.并行線程數(shù)默認(rèn)值:
CPU<=8:等于CPU核數(shù);CPU>8:(3+CPU核數(shù)*5)/8;指定-XX:ParallelGCThreads=4
c.會(huì)根據(jù)MinorGC的頻率、時(shí)間等動(dòng)態(tài)調(diào)整Eden/S0/S1的大小,可通過(guò)參數(shù)-XX:+useAdaptiveSizePolicy取消這一特性 - 適用場(chǎng)景:多CPU、Server級(jí)別(2核2G內(nèi)存)機(jī)器、吞吐量要求高
- 對(duì)象直接分配在Old(老年代)的情況:
a.在TLAB和Eden上分配失敗,且對(duì)象大于Eden的一般大小
b.晉升老年代對(duì)象年齡(-XX:PretenureSizeThreshold)參數(shù)是無(wú)效的 - 對(duì)象晉升規(guī)則:經(jīng)歷多次MinorGC仍存活的對(duì)象;To空間不足對(duì)象直接晉升
3.老年代可用GC
3.1.Serial Old收集器(串行老年代收集器)
Serial Old收集器是Serial收集器的老年代版本,它同樣是一個(gè)單線程收集器。使用“標(biāo)記-整理”算法,這個(gè)收集器的主要意義也是在于給Client模式下的虛擬機(jī)使用。如果在Server模式下,有兩大用途:一種用途是在JDK1.5以及之前的版本中與Parallel Scavenge收集器搭配使用,另一個(gè)用途就是作為CMS收集器的后備預(yù)案,在并發(fā)收集發(fā)生Concurrent Mode Failure時(shí)使用。

- 特性:Serial(串行)、Stop The World(暫停所有的工作線程)
- 算法:“標(biāo)記-整理”算法
- 場(chǎng)景:Client模式下使用、與Parallel Scavenge收集器搭配使用和CMS收集器的預(yù)備方案
3.2.Parallel Old收集器(并行老年代收集器)
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多線程和“標(biāo)記-整理”算法。在注重吞吐量以及CPU資源敏感的場(chǎng)合,都可以優(yōu)先考慮Parallel Scavenge和Parallel Old收集器的組合。

- 特性:Parallel(并行)、Stop The World(暫停所有的工作線程)
- 算法:“標(biāo)記-整理”算法
- 場(chǎng)景:注重吞吐量以及CPU資源敏感的場(chǎng)合
3.3.CMS收集器(并發(fā)標(biāo)記清除收集器)
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。目前很大一部分的Java應(yīng)用集中在互聯(lián)網(wǎng)站或者B/S系統(tǒng)的服務(wù)端,這類應(yīng)用尤其重視服務(wù)的響應(yīng)速度,希望系統(tǒng)停頓時(shí)間最短,以給用戶帶來(lái)較好的體驗(yàn)。

CMS收集器是基于“標(biāo)記-清除”算法實(shí)現(xiàn)的,它的運(yùn)作過(guò)程分為4個(gè)步驟:
- 初始標(biāo)記(Initial Mark):標(biāo)記GC Roots能直接關(guān)聯(lián)到的對(duì)象,耗時(shí)短
- 并發(fā)標(biāo)記(Concurrent Mark):從GC Roots出發(fā),并發(fā)地標(biāo)記可達(dá)對(duì)象
- 重新標(biāo)記(Remark):修正并發(fā)標(biāo)記期間因程序運(yùn)行而重新關(guān)聯(lián)到GC Roots的對(duì)象標(biāo)記
- 并發(fā)清除(Concurrent Sweep): 并行地進(jìn)行無(wú)用對(duì)象的回收
CMS是一款優(yōu)秀的收集器,但是CMS還遠(yuǎn)不達(dá)到完美的程度,它有3個(gè)明顯的缺點(diǎn):
- CMS收集器對(duì)CPU資源非常敏感:CMS默認(rèn)啟動(dòng)的回收線程數(shù)是(CPU數(shù)量 + 3)/ 4,即當(dāng)CPU在4個(gè)以上時(shí),并發(fā)回收時(shí)垃圾收集線程不少于25%的CPU資源,并且隨著CPU數(shù)量的增加而下降。但是當(dāng)CPU不足4個(gè)時(shí),CMS對(duì)用戶程序的影響就會(huì)很大,降低了程序的響應(yīng)速度。
- CMS收集器無(wú)法處理浮動(dòng)垃圾(Floating Garbage),可能出現(xiàn)“Concurrent Mode Failure”失敗而導(dǎo)致另一次Full GC的產(chǎn)生。
a.并發(fā)清除階段用戶線程不斷運(yùn)行就會(huì)產(chǎn)生新的垃圾,只能等下一次GC時(shí)被收集;
b.并發(fā)清除階段需要給與用戶線程預(yù)留足夠的內(nèi)存空間運(yùn)行,不能等到老年代被填滿了再進(jìn)行回收。可設(shè)置參數(shù):-XX:CMSInitiatingOccupancyFraction=68指定當(dāng)老年代使用了68%的內(nèi)存空間后就會(huì)觸發(fā)Full GC。在JDK1.6中,CMS收集器的啟動(dòng)閾值已提升至92%,要是CMS運(yùn)行期間預(yù)留的內(nèi)存無(wú)法滿足程序需要就會(huì)出現(xiàn)一次“Concurrent Mode Failure”失敗,這時(shí)虛擬機(jī)將啟動(dòng)預(yù)備方案:臨時(shí)啟用Serial Old收集器來(lái)重新進(jìn)行老年代的垃圾收集。 - 內(nèi)存碎片:CMS收集器是基于“標(biāo)記-清除”算法實(shí)現(xiàn)的,這意味著收集結(jié)束后會(huì)有大量的空間碎片產(chǎn)生??臻g碎片過(guò)多時(shí),將會(huì)給大對(duì)象分配帶來(lái)很大麻煩,往往會(huì)出現(xiàn)老年代還有很大的空間剩余,但是無(wú)法找到足夠大的連續(xù)空間來(lái)分配當(dāng)前對(duì)象,不得不提前觸發(fā)一次Full GC。
a.-XX:+UseCMSCompactAtFullCollection:每次Full GC后進(jìn)行內(nèi)存壓縮
b.-XX:CMSFullGCsBeforeCompaction:多少次Full GC后進(jìn)行一次內(nèi)存壓縮
4.G1收集器(Garbage-First收集器)
G1收集器是一款面向服務(wù)端應(yīng)用的垃圾收集器。HotSpot開(kāi)發(fā)團(tuán)隊(duì)賦予它的使命是未來(lái)可以替換掉JDK1.5中發(fā)布的CMS收集器。與其他GC收集器相比,G1收集器具備以下特點(diǎn):
- 并行與并發(fā):G1收集器能充分利用多CPU、多核環(huán)境下的硬件優(yōu)勢(shì),使用多個(gè)CPU來(lái)縮短Stop-The-World停頓的時(shí)間,部分其他收集器原本需要暫停Java線程執(zhí)行的GC動(dòng)作,G1收集器仍然可以通過(guò)并發(fā)的方式讓Java程序繼續(xù)執(zhí)行。
- 分代收集:與其他收集器一樣,分代概念在G1中依然得以保留。
- 空間整合:與CMS的“標(biāo)記-清除”算法不同,G1從整體來(lái)看是基于“標(biāo)記-整理”算法實(shí)現(xiàn)的收集器,從局部(兩個(gè)Region之間)上來(lái)看是基于“復(fù)制”算法實(shí)現(xiàn)的。但無(wú)論如何,這兩種算法都意味著G1運(yùn)作期間不會(huì)產(chǎn)生內(nèi)存空間碎片,收集后能提供規(guī)整的可用內(nèi)存,有利于程序長(zhǎng)時(shí)間運(yùn)行,分配大對(duì)象時(shí)不會(huì)因?yàn)闊o(wú)法找到連續(xù)內(nèi)存空間而提前觸發(fā)下一次GC。
- 可預(yù)測(cè)的停頓:這是G1相對(duì)于CMS的另一大優(yōu)勢(shì),降低停頓時(shí)間是G1和CMS共同的關(guān)注點(diǎn),但G1除了追求低停頓時(shí)間外,還能建立可預(yù)測(cè)的停頓時(shí)間模型,能讓使用者明確指定在一個(gè)長(zhǎng)度為M毫秒的時(shí)間片斷內(nèi),消耗在垃圾收集上的時(shí)間不得超過(guò)N毫秒。
在G1之前的其他收集器進(jìn)行收集的范圍都是整個(gè)新生代或老年代,而G1不再是這樣。使用G1收集器時(shí),Java堆的內(nèi)存布局就與其他收集器有很大差別,它將整個(gè)Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region),雖然還保留著新生代和老年代的概念,但它們已不再是物理隔離的了,它們都是一部分Region的集合。
G1收集器之所以能建立可預(yù)測(cè)的停頓時(shí)間模型,是因?yàn)樗梢杂杏?jì)劃地避免在整個(gè)Java堆中進(jìn)行全區(qū)域的垃圾收集。G1跟蹤各個(gè)Region里面的垃圾堆積的價(jià)值大?。ɑ厥账@得的空間大小以及回收所需時(shí)間的經(jīng)驗(yàn)值),在后臺(tái)維護(hù)一個(gè)優(yōu)先列表,每次根據(jù)允許的收集時(shí)間,優(yōu)先回收價(jià)值最大的Region。這種使用Region劃分內(nèi)存空間以及有優(yōu)先級(jí)的區(qū)域回收方式,保證了G1收集器在有限的時(shí)間內(nèi)可以獲取盡可能高的收集效率。
把Java堆分為多個(gè)Region后,垃圾收集是否真的就能以Region為單位進(jìn)行了?聽(tīng)起來(lái)順理成章,再仔細(xì)想想就會(huì)發(fā)現(xiàn)問(wèn)題所在:Region不可能是孤立的。一個(gè)對(duì)象分配在某個(gè)Region中,它并非只能被本Region中的其他對(duì)象引用,而是可以與整個(gè)Java堆任意的對(duì)象發(fā)生引用關(guān)系。那在做可達(dá)性判定確定對(duì)象是否存活的時(shí)候,豈不是還得掃描整個(gè)Java堆才能保證準(zhǔn)確性?這個(gè)問(wèn)題其實(shí)并非在G1中才有,只是G1中更加突出而已。在G1收集器中,Region之間的對(duì)象引用以及其他收集器中的新生代與老年代之間的對(duì)象引用,虛擬機(jī)都是使用Remembered Set來(lái)避免全堆掃描。G1中每個(gè)Region都有一個(gè)與之對(duì)應(yīng)的Remembered Set,虛擬機(jī)發(fā)現(xiàn)程序在對(duì)Reference類型的數(shù)據(jù)進(jìn)行寫操作時(shí),會(huì)產(chǎn)生一個(gè)Write Barrier暫時(shí)中斷寫操作,檢查Reference引用的對(duì)象是否處于不同的Region之中,如果是,便通過(guò)CardTable把相關(guān)引用信息記錄到被引用對(duì)象所屬的Region的Remembered Set之中。當(dāng)進(jìn)行內(nèi)存回收時(shí),在GC根節(jié)點(diǎn)的枚舉范圍中加入Remembered Set即可保證不對(duì)全堆掃描也不會(huì)有遺漏。
如果不計(jì)算維護(hù)Remembered Set的操作,G1收集器的運(yùn)作大致可劃分為一下幾個(gè)步驟:
- 初始標(biāo)記(Initial Mark):僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象,并且修改TAMS(Next Top at Mark Start)的值,讓下一階段用戶程序并發(fā)運(yùn)行時(shí),能在正確可用的Region中創(chuàng)建新對(duì)象,這階段需要停頓線程,但耗時(shí)很短。
- 并發(fā)標(biāo)記(Concurrent Mark):是從GC Roots開(kāi)始對(duì)堆中對(duì)象進(jìn)行可達(dá)性分析,找出存活的對(duì)象,這階段耗時(shí)較長(zhǎng),但可與用戶程序并發(fā)執(zhí)行。
- 最終標(biāo)記(Final Mark):是為了修正在并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的哪一部分標(biāo)記記錄,虛擬機(jī)將這段時(shí)間對(duì)象變化記錄在線程Remembered Set Logs里面,最終標(biāo)記階段需要把Remembered Set Logs的數(shù)據(jù)合并到Remembered Set中,這個(gè)階段需要停頓線程,但是可并行執(zhí)行。
- 篩選回收(Live Data Counting and Evacuation):首先對(duì)各個(gè)Region的回收價(jià)值和成本進(jìn)行排序,根據(jù)用戶所期望的GC停頓時(shí)間來(lái)制定回收計(jì)劃。

5.理解GC日志
閱讀GC日志是處理Java虛擬機(jī)內(nèi)存問(wèn)題的基礎(chǔ)技能,GC日志只是一些人為確定的規(guī)則,沒(méi)有太多技術(shù)含量。每一種收集器的日志形式都是由它們自身的實(shí)現(xiàn)所決定的,但虛擬機(jī)設(shè)計(jì)者為了方便用戶閱讀,將各個(gè)收集器的日志都維持一定的共性。例如一下兩段典型的GC日志:
33.125:[GC [DefNew:3324K->152k(3712k),0.0025925 secs] 3324K->152K(11904K) 0.0031680 secs]
100.667:[Full GC [Tenured:0K->210K(10240K),0.0149142 secs] 4603K->210K(19456K),[Perm:2999K->2999K(21248K)],0.0150007 secs] [Times:user=0.01 sys=0.00,real=0.02 secs]
- 最前面的數(shù)字“33.125”和“100.667”:代表了GC發(fā)生的時(shí)間,這個(gè)數(shù)字的含義是從Java虛擬機(jī)啟動(dòng)以來(lái)經(jīng)過(guò)的秒數(shù)。
- GC日志開(kāi)頭的“[GC”和“[Full GC”說(shuō)明了這次GC的停頓類型。如果有“Full”說(shuō)明這次GC是發(fā)生了Stop-The-World的。
- 接下來(lái)的“[DefNew”、“[Tenured”、“[Perm”表示GC發(fā)生的區(qū)域,這里顯示的區(qū)域名稱與使用的GC收集器是密切相關(guān)的。DefNew:是Serial收集器中新生代的名稱縮寫(Default New Generation),ParNew:是ParNew收集器中新生代的名稱縮寫(Parallel New Generation),PSYoungGen:是Parallel Scavenge收集器中新生代名稱的縮寫。
- 后面方括號(hào)內(nèi)部的“3324K->152K(3712K)”含義是“GC前該內(nèi)存區(qū)域已使用容量->GC后該內(nèi)存區(qū)域已使用容量(該內(nèi)存區(qū)域總?cè)萘浚薄6诜嚼ㄌ?hào)之外的“3324K->152K(11904K)”表示“GC前Java堆已使用容量->GC后Java堆已使用容量(Java堆總?cè)萘浚?/li>
- 再往后,“0.0025925 secs”表示該內(nèi)存區(qū)域GC所占用的時(shí)間,單位是秒。有的收集器會(huì)給出更具體的時(shí)間數(shù)據(jù),如“[Times:user=0.01 sys=0.00,real=0.02 secs]”,這里面的user、sys和real與Linux的time命令所輸出的時(shí)間含義一致,分別代表用戶態(tài)消耗的CPU時(shí)間、內(nèi)核態(tài)消耗的CPU時(shí)間和操作從開(kāi)始到結(jié)束所經(jīng)過(guò)的墻鐘時(shí)間(Wall Clock Time)。CPU時(shí)間和墻鐘時(shí)間的區(qū)別是:墻鐘時(shí)間包括各種非運(yùn)算的等待耗時(shí),例如等待磁盤I/O、等待線程時(shí)間,而CPU時(shí)間不包括這些耗時(shí),但當(dāng)系統(tǒng)有多CPU或者多核的話,多線程操作會(huì)疊加這些CPU時(shí)間,所以看到user或sys時(shí)間超過(guò)real時(shí)間是完全正常的。
6.垃圾收集器參數(shù)總結(jié)
| 參數(shù) | 描述 |
|---|---|
| UseSerialGC | 虛擬機(jī)運(yùn)行在Client模式下的默認(rèn)值,設(shè)置后,使用Serial+Serial Old的收集器組合進(jìn)行內(nèi)存回收 |
| UseParNewGC | 設(shè)置后,使用ParNew和Serial Old兩種組合的垃圾收集器進(jìn)行GC |
| UseConcMarkSweepGC | 設(shè)置后,使用ParNew、CMS和Serial Old組合的垃圾收集器進(jìn)行GC |
| UseParallelGC | 虛擬機(jī)運(yùn)行在Server模式下的默認(rèn)值,設(shè)置后,使用Parallel Scavenge 和Serial Old的組合垃圾收集器進(jìn)行GC |
| SurvivorRatio | 年輕代中Eden Space和Survivor Space區(qū)域的容量比值,默認(rèn)為8,即Eden:Survivor=8:1 |
| PretenureSizeThreshold | 代表直接進(jìn)入老年代中的對(duì)象大小,設(shè)置此值后,大于這個(gè)參數(shù)的對(duì)象將直接在老年代中進(jìn)行內(nèi)存分配 |
| MaxTenuringThreshold | 在分代GC算法中,此值代表對(duì)象轉(zhuǎn)移到年老代中的年齡,每個(gè)對(duì)象經(jīng)歷過(guò)一次年輕代GC(Minor GC)后,年齡就加1,到超過(guò)設(shè)置的值后,對(duì)象轉(zhuǎn)移到老年代 |
| UseAdaptiveSizePolicy | 動(dòng)態(tài)調(diào)整Java堆中各個(gè)區(qū)域的大小以及進(jìn)行年老代的年齡 |
| HandlePromotionFailure | 是否允許分配擔(dān)保失敗,即老年代中的剩余內(nèi)存空間不足以應(yīng)付年輕代的整個(gè)Eden和Survivor Space的所有對(duì)象都存活的極端情況 |
| ParallelGCThreads | 設(shè)置并行GC時(shí)進(jìn)行內(nèi)存回收的線程數(shù)量 |
| GCTimeRatio | GC時(shí)間占總時(shí)間的比率,默認(rèn)值是99, 即允許1%的GC時(shí)間。僅在使用Parallel Scavenge收集器時(shí)生效 |
| MaxGCPauseMillis | 設(shè)置GC的最大停頓時(shí)間。僅在使用Parallel Scavenge收集器時(shí)生效 |
| CMSInitiatingOccupancyFraction | 設(shè)置CMS收集器在老年代時(shí)間被使用多少后觸發(fā)垃圾收集。默認(rèn)值為68%,僅在使用CMS收集器時(shí)生效 |
| UseCMSCompactAtFullCollection | 設(shè)置CMS收集器在完成垃圾收集后是否要進(jìn)行一次內(nèi)存碎片整理。僅在使用CMS收集器時(shí)生效 |
| CMSFullGCsBeforeCompaction | 設(shè)置CMS收集器在進(jìn)行若干次垃圾收集后再啟動(dòng)一次內(nèi)存碎片整理,僅在使用CMS收集器時(shí)生效 |
7.內(nèi)存分配與回收策略
Java技術(shù)體系所提倡的自動(dòng)內(nèi)存管理最終可以歸結(jié)為自動(dòng)化地解決了兩個(gè)問(wèn)題:給對(duì)象分配內(nèi)存以及回收分配給對(duì)象的內(nèi)存,接下來(lái)我們來(lái)介紹幾條最普通的內(nèi)存分配規(guī)則。
- 對(duì)象優(yōu)先在Eden分配
大多數(shù)情況下,對(duì)象在新生代Eden區(qū)中分配。當(dāng)Eden區(qū)沒(méi)有足夠空間進(jìn)行分配時(shí),虛擬機(jī)將發(fā)起一次Minor GC。虛擬機(jī)提供了-XX:+PrintGCDetails這個(gè)收集器日志參數(shù),告訴虛擬機(jī)在發(fā)生垃圾收集行為時(shí)打印內(nèi)存回收日志,并且在進(jìn)程退出的時(shí)候輸出當(dāng)前的內(nèi)存各區(qū)域分配情況。 - 大對(duì)象直接進(jìn)入老年代
所謂的大對(duì)象是指,需要大量連續(xù)內(nèi)存空間的Java對(duì)象,最典型的大對(duì)象就是那種很長(zhǎng)的字符串以及數(shù)組。虛擬機(jī)提供了一個(gè)-XX:PretenureSizeThreshold參數(shù),令大于這個(gè)設(shè)置值的對(duì)象直接在老年代分配。這樣做的目的是避免在Eden區(qū)及兩個(gè)Survivor區(qū)之間發(fā)生大量的內(nèi)存復(fù)制。 - 長(zhǎng)期存活的對(duì)象將進(jìn)入老年代
虛擬機(jī)提供了一個(gè)-XX:MaxTenuringThreshold參數(shù)設(shè)置對(duì)象晉升老年代的年齡閾值。如果對(duì)象在Eden出生并經(jīng)過(guò)第一次Minor GC后仍然存活,并且能被Survivor容納的話,將被移動(dòng)到Survivor空間中,并且對(duì)象年齡設(shè)為1。對(duì)象在Survivor區(qū)中每“熬過(guò)”一次Minor GC,年齡就增加一,當(dāng)它的年齡增加到一定程度(默認(rèn)為15歲)就將會(huì)被晉升到老年代中。 - 動(dòng)態(tài)對(duì)象年齡判定
為了能更好地適應(yīng)不同程序的內(nèi)存狀況,虛擬機(jī)并不是永遠(yuǎn)地要求對(duì)象的年齡必須達(dá)到了MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對(duì)象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對(duì)象就可以直接進(jìn)入老年代,無(wú)須等到MaxTenuringThreshold中要求的年齡。 - 空間分配擔(dān)保
在發(fā)生Minor GC之前,虛擬機(jī)會(huì)檢查老年代最大可用的連續(xù)空間是否大于新生代所有對(duì)象總空間,如果這個(gè)條件成立,那么Minor GC可以確保是安全的。如果不成立,則虛擬機(jī)會(huì)查看HandlePromotionFailure設(shè)置值是否允許擔(dān)保失敗。如果允許,那么會(huì)繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對(duì)象的平均大小。如果大于,將嘗試著進(jìn)行一次Minor GC;如果小于,或者HandlePromotionFailure設(shè)置不允許冒險(xiǎn),那這是也要改為進(jìn)行一次Full GC。