細(xì)說JVM(垃圾收集器與內(nèi)存分配)

一、基礎(chǔ)性的概念

1呛牲、Minor GC 和 Full GC
  • 新生代GC(Minor GC):指發(fā)生在新生代的垃圾收集動作鞍恢,因?yàn)镴ava對象大多都具備朝生夕滅的特性希太,所以Minor GC非常頻繁狼速,一般回收速度也比較快琅锻。

  • 老年代GC(Major GC / Full GC):指發(fā)生在老年代的GC,出現(xiàn)了Major GC向胡,經(jīng)常會伴隨至少一次的Minor GC(但非絕對的恼蓬,在Parallel Scavenge收集器的收集策略里就有直接進(jìn)行Major GC的策略選擇過程)。Major GC的速度一般會比Minor GC慢10倍以上僵芹。

2处硬、并發(fā)和并行

這兩個名詞都是并發(fā)編程中的概念,在談?wù)摾占鞯纳舷挛恼Z境中拇派,它們可以解釋如下郁油。

  • 并行(Parallel):指多條垃圾收集線程并行工作,但此時用戶線程仍然處于等待狀態(tài)攀痊。

  • 并發(fā)(Concurrent):指用戶線程與垃圾收集線程同時執(zhí)行(但不一定是并行的,可能會交替執(zhí)行)拄显,用戶程序在繼續(xù)運(yùn)行苟径,而垃圾收集程序運(yùn)行于另一個CPU上。

3躬审、吞吐量

吞吐量就是CPU用于運(yùn)行用戶代碼的時間與CPU總消耗時間的比值棘街,即吞吐量 = 運(yùn)行用戶代碼時間 /(運(yùn)行用戶代碼時間 + 垃圾收集時間)。
虛擬機(jī)總共運(yùn)行了100分鐘承边,其中垃圾收集花掉1分鐘遭殉,那吞吐量就是99%。

二博助、垃圾收集器

我發(fā)現(xiàn)有很多垃圾收集器總結(jié)的非常好的博客险污,感覺自己再照著書上寫一遍也沒有什么意義,這里就放幾個鏈接富岳,我發(fā)現(xiàn)的比較好的對于垃圾收集器整理的很完善的文章:

上面的三篇文章中對于垃圾收集器做了很系統(tǒng)的整理蛔糯,我這里再補(bǔ)充一個表格,是常用垃圾收集器組合的表格:

新生代GC 老年代GC 說明
組合一 Serial Serial Old Serial和Serial Old都是單線程進(jìn)行GC窖式,特點(diǎn)就是GC時暫停所有應(yīng)用線程蚁飒。
組合二 Serial CMS+Serial Old CMS(Concurrent Mark Sweep)是并發(fā)GC,實(shí)現(xiàn)GC線程和應(yīng)用線程并發(fā)工作萝喘,不需要暫停所有應(yīng)用線程淮逻。另外琼懊,當(dāng)CMS進(jìn)行GC失敗時,會自動使用Serial Old策略進(jìn)行GC爬早。
組合三 ParNew CMS 使用-XX:+UseParNewGC選項(xiàng)來開啟哼丈。ParNew是Serial的并行版本,可以指定GC線程數(shù)凸椿,默認(rèn)GC線程數(shù)為CPU的數(shù)量削祈。可以使用-XX:ParallelGCThreads選項(xiàng)指定GC的線程數(shù)脑漫。如果指定了選項(xiàng)-XX:+UseConcMarkSweepGC選項(xiàng)髓抑,則新生代默認(rèn)使用ParNew GC策略。
組合四 ParNew Serial Old 使用-XX:+UseParNewGC選項(xiàng)來開啟优幸。新生代使用ParNew GC策略吨拍,年老代默認(rèn)使用Serial Old GC策略。
組合五 Parallel Scavenge Serial Old Parallel Scavenge策略主要是關(guān)注一個可控的吞吐量:應(yīng)用程序運(yùn)行時間 / (應(yīng)用程序運(yùn)行時間 + GC時間)网杆,可見這會使得CPU的利用率盡可能的高羹饰,適用于后臺持久運(yùn)行的應(yīng)用程序,而不適用于交互較多的應(yīng)用程序碳却。
組合六 Parallel Scavenge Parallel Old Parallel Old是Serial Old的并行版本
組合七 G1GC G1GC -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC(開啟)XX:MaxGCPauseMillis =50(暫停時間目標(biāo))-XX:GCPauseIntervalMillis =200(暫停間隔目標(biāo))-XX:+G1YoungGenSize=512m(新生代大卸又取)-XX:SurvivorRatio=6(Eden區(qū)和Survivor區(qū)的比例)

最后在補(bǔ)充一篇文章:
JVM 垃圾回收器工作原理及使用實(shí)例介紹

三、內(nèi)存分配與回收策略

1昼浦、對象優(yōu)先在Eden區(qū)分配

大多數(shù)情況下馍资,對象在新生代的Eden區(qū)中分配。當(dāng)Eden區(qū)沒有足夠空間進(jìn)行分配時关噪,虛擬機(jī)將發(fā)起一次Minor GC鸟蟹。

2、大對象直接進(jìn)入老年代

所謂的大對象是指使兔,需要大量連續(xù)內(nèi)存空間的Java對象建钥,最典型的大對象就是那種很長的字符串以及數(shù)組。

大對象對虛擬機(jī)的內(nèi)存分配來說就是一個壞消息(比遇到一個大對象更加壞的消息就是遇到一群“朝生夕滅”的“短命大對象”虐沥,寫程序的時候應(yīng)當(dāng)避免)熊经,經(jīng)常出現(xiàn)大對象容易導(dǎo)致內(nèi)存還有不少空間時就提前觸發(fā)垃圾收集以獲取足夠的連續(xù)空間來“安置”它們。

虛擬機(jī)提供了一個-XX:PretenureSizeThreshold參數(shù)置蜀,令大于這個設(shè)置值的對象直接在老年代分配奈搜。這樣做的目的是避免在Eden區(qū)及兩個Survivor區(qū)之間發(fā)生大量的內(nèi)存復(fù)制。

注意:PretenureSizeThreshold參數(shù)只對Serial和ParNew兩款收集器有效盯荤,Parallel Scavenge收集器不認(rèn)識這個參數(shù)馋吗,Parallel Scavenge收集器一般并不需要設(shè)置。如果遇到必須使用此參數(shù)的場合秋秤,可以考慮ParNew加CMS的收集器組合宏粤。

上面都是書上的內(nèi)容脚翘,并且書上給出了一個例子:

private static final int _1MB = 1024 * 1024;   
/**  
* VM參數(shù):-verbose:gc(和-XX:+PrintGC具有同樣的作用) -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 
* -XX:PretenureSizeThreshold=3145728 
*/  
public static void testPretenureSizeThreshold() {  
    byte[] allocation;  
    allocation = new byte[4 * _1MB];  //直接分配在老年代中  
}

運(yùn)行結(jié)果:

Heap  
    def new generation   total 9216K, used 671K [0x029d0000, 0x033d0000, 0x033d0000)  
    eden space 8192K,   8% used [0x029d0000, 0x02a77e98, 0x031d0000)  
    from space 1024K,   0% used [0x031d0000, 0x031d0000, 0x032d0000)  
    to   space 1024K,   0% used [0x032d0000, 0x032d0000, 0x033d0000)  
    tenured generation   total 10240K, used 4096K [0x033d0000, 0x03dd0000, 0x03dd0000)  
    the space 10240K,  40% used [0x033d0000, 0x037d0010, 0x037d0200, 0x03dd0000)  
    compacting perm gen  total 12288K, used 2107K [0x03dd0000, 0x049d0000, 0x07dd0000)  
    the space 12288K,  17% used [0x03dd0000, 0x03fdefd0, 0x03fdf000, 0x049d0000)  
    No shared spaces configured.

書中給出的解釋:
執(zhí)行代碼中的testPretenureSizeThreshold()方法后,我們看到Eden空間幾乎沒有被使用绍哎,而老年代的10MB空間被使用了40%来农,也就是4MB的allocation對象直接就分配在老年代中,這是因?yàn)镻retenureSizeThreshold被設(shè)置為3MB(就是3145728崇堰,這個參數(shù)不能像-Xmx之類的參數(shù)一樣直接寫3MB)沃于,因此超過3MB的對象都會直接在老年代進(jìn)行分配。

我的一個疑問:
我發(fā)現(xiàn)-XX:PretenureSizeThreshold這個參數(shù)并沒有默認(rèn)值海诲,那么如果不設(shè)置這個參數(shù)繁莹,虛擬機(jī)會如何做?
我經(jīng)過查找發(fā)現(xiàn)了一篇解決我的疑問的文章:jvm對大對象分配內(nèi)存的特殊處理
原來如果不設(shè)置-XX:PretenureSizeThreshold參數(shù)的話特幔,當(dāng)對象大小大于Eden區(qū)的時候會直接扔到老年代咨演。我自己也做了一些實(shí)驗(yàn),發(fā)現(xiàn)確實(shí)是這樣蚯斯。

3薄风、長期存活的對象將進(jìn)入老年代

既然虛擬機(jī)采用了分代收集的思想來管理內(nèi)存,那么內(nèi)存回收時就必須能識別哪些對象應(yīng)放在新生代拍嵌,哪些對象應(yīng)放在老年代中遭赂。為了做到這點(diǎn),虛擬機(jī)給每個對象定義了一個對象年齡(Age)計(jì)數(shù)器横辆。如果對象在Eden出生并經(jīng)過第一次Minor GC后仍然存活嵌牺,并且能被Survivor容納的話,將被移動到Survivor空間中龄糊,并且對象年齡設(shè)為1。對象在Survivor區(qū)中每“熬過”一次Minor GC募疮,年齡就增加1歲炫惩,當(dāng)它的年齡增加到一定程度(默認(rèn)為15歲),就將會被晉升到老年代中阿浓。對象晉升老年代的年齡閾值他嚷,可以通過參數(shù)-XX:MaxTenuringThreshold設(shè)置。

4芭毙、動態(tài)對象年齡判定

為了能更好地適應(yīng)不同程序的內(nèi)存狀況筋蓖,虛擬機(jī)并不是永遠(yuǎn)地要求對象的年齡必須達(dá)到了MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半退敦,年齡大于或等于該年齡的對象就可以直接進(jìn)入老年代粘咖,無須等到MaxTenuringThreshold中要求的年齡。

5侈百、空間分配擔(dān)保

在發(fā)生Minor GC之前瓮下,虛擬機(jī)會先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對象總空間翰铡,如果這個條件成立,那么Minor GC可以確保是安全的讽坏。如果不成立锭魔,則虛擬機(jī)會查看HandlePromotionFailure設(shè)置值是否允許擔(dān)保失敗。如果允許路呜,那么會繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對象的平均大小迷捧,如果大于,將嘗試著進(jìn)行一次Minor GC胀葱,盡管這次Minor GC是有風(fēng)險的漠秋;如果小于,或者HandlePromotionFailure設(shè)置不允許冒險巡社,那這時也要改為進(jìn)行一次Full GC膛堤。

前面提到過,新生代使用復(fù)制收集算法晌该,但為了內(nèi)存利用率肥荔,只使用其中一個Survivor空間來作為輪換備份,因此當(dāng)出現(xiàn)大量對象在Minor GC后仍然存活的情況(最極端的情況就是內(nèi)存回收后新生代中所有對象都存活)朝群,就需要老年代進(jìn)行分配擔(dān)保燕耿,把Survivor無法容納的對象直接進(jìn)入老年代。與生活中的貸款擔(dān)保類似姜胖,老年代要進(jìn)行這樣的擔(dān)保誉帅,前提是老年代本身還有容納這些對象的剩余空間,一共有多少對象會活下來在實(shí)際完成內(nèi)存回收之前是無法明確知道的右莱,所以只好取之前每一次回收晉升到老年代對象容量的平均大小值作為經(jīng)驗(yàn)值蚜锨,與老年代的剩余空間進(jìn)行比較,決定是否進(jìn)行Full GC來讓老年代騰出更多空間慢蜓。

取平均值進(jìn)行比較其實(shí)仍然是一種動態(tài)概率的手段亚再,也就是說,如果某次Minor GC存活后的對象突增晨抡,遠(yuǎn)遠(yuǎn)高于平均值的話氛悬,依然會導(dǎo)致?lián)J。℉andle Promotion Failure)耘柱。如果出現(xiàn)了HandlePromotionFailure失敗如捅,那就只好在失敗后重新發(fā)起一次Full GC。雖然擔(dān)保失敗時繞的圈子是最大的调煎,但大部分情況下都還是會將HandlePromotionFailure開關(guān)打開镜遣,避免Full GC過于頻繁。
注意:在JDK 6 Update 24版本之后士袄,HandlePromotionFailure參數(shù)就沒有作用了烈涮,因?yàn)橐?guī)則變?yōu)榱耍褐灰夏甏倪B續(xù)空間大于新生代對象的總大小或者歷次晉升的對象的平均大小朴肺,就進(jìn)行Minor GC 否則進(jìn)行Full GC。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末坚洽,一起剝皮案震驚了整個濱河市戈稿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌讶舰,老刑警劉巖鞍盗,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異跳昼,居然都是意外死亡般甲,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門鹅颊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來敷存,“玉大人,你說我怎么就攤上這事堪伍∶常” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵帝雇,是天一觀的道長涮俄。 經(jīng)常有香客問我,道長尸闸,這世上最難降的妖魔是什么彻亲? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮吮廉,結(jié)果婚禮上苞尝,老公的妹妹穿的比我還像新娘。我一直安慰自己宦芦,他們只是感情好野来,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著踪旷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪豁辉。 梳的紋絲不亂的頭發(fā)上令野,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機(jī)與錄音徽级,去河邊找鬼气破。 笑死,一個胖子當(dāng)著我的面吹牛餐抢,可吹牛的內(nèi)容都是我干的现使。 我是一名探鬼主播低匙,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼碳锈!你這毒婦竟也來了顽冶?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤售碳,失蹤者是張志新(化名)和其女友劉穎强重,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贸人,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡间景,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了艺智。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倘要。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖十拣,靈堂內(nèi)的尸體忽然破棺而出封拧,到底是詐尸還是另有隱情,我是刑警寧澤父晶,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布哮缺,位于F島的核電站,受9級特大地震影響甲喝,放射性物質(zhì)發(fā)生泄漏尝苇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一埠胖、第九天 我趴在偏房一處隱蔽的房頂上張望糠溜。 院中可真熱鬧,春花似錦直撤、人聲如沸非竿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽红柱。三九已至,卻和暖如春蓖乘,著一層夾襖步出監(jiān)牢的瞬間锤悄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工嘉抒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留零聚,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像隶症,于是被迫代替她去往敵國和親政模。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容