內(nèi)存分配策略

前言

在開始介紹內(nèi)存分配策略之前,先啰嗦一下gc日志相關(guān)內(nèi)容,要知道會讀gc日志是處理java虛擬機內(nèi)存問題的一項基本技能。接下來以一段gc日志為例,詳細(xì)介紹下日志相關(guān)內(nèi)容:

[GC (Allocation Failure) --[PSYoungGen: 8192K->8192K(9216K)] 12288K->16392K(19456K), 0.0038111 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 8192K->2731K(9216K)] [ParOldGen: 8200K->8193K(10240K)] 16392K->10924K(19456K), [Metaspace: 3334K->3334K(1056768K)], 0.0056151 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 

:其實gc日志的格式是跟垃圾收集器有關(guān)的,不同的收集器,它們的格式可能都是不一樣的,但是JVM的設(shè)計者為了方便程序員們閱讀,將各個收集器的日志做了格式統(tǒng)一。

  1. gc日志開頭的[GC[Full GC代表這次垃圾收集的類型,需要注意的是,它并不是用來區(qū)分是新生代的gc還是老年代gc的,如果是Full GC,只能說明這次gc是發(fā)生了STW(Stop-The-World)的;

  2. 接下來會看到[PSYoungGen[ParOldGen[Metaspace表示gc發(fā)生的區(qū)域,當(dāng)然這里的區(qū)域名與垃圾收集器是相關(guān)的:

    • 如果是Serial收集器,那么新生代名稱為Default New Generation,gc日志顯示[DefNew
    • 如果是ParNew收集器,新生代的名稱變成Parallel New Gneration,gc日志顯示[ParNew
    • 如果是Parallel Scavenge收集器,gc日志則顯示[PSYoungGen
  3. 區(qū)域名稱后緊跟著8192K->8192K(9216K),它的意思是在gc前該內(nèi)存區(qū)域已使用的量 -> gc后該內(nèi)存區(qū)域的使用量(該內(nèi)存區(qū)域總?cè)萘浚T诜嚼ㄌ柡竺婢o跟著12288K->16392K(19456K),它表示gc heap已使用的量 -> gc后heap的使用量(heap的總?cè)萘浚?/p>

  4. 在各個區(qū)域的gc相關(guān)內(nèi)存變化之后,會給出該內(nèi)存區(qū)域gc所用時間,有的收集器會給出具體的gc耗時時間數(shù)據(jù),比如[Times: user=0.02 sys=0.00, real=0.01 secs],可以看到,該時間數(shù)據(jù)包括3個時間:

    • user:用戶態(tài)消耗的cpu時間;
    • sys:內(nèi)核態(tài)消耗的cpu時間;
    • real:操作從開始到結(jié)束所經(jīng)過的實際時間

:需要注意的是,real時間包括各種非運算的等待耗時,比如等待磁盤I/O,但是cpu時間是不包括這些耗時的。可能熟悉gc日志的同學(xué)可能會問,既然real的時間包括等待時間,user和sys不包括等待時間,那為什么好多時候user或者sys的時間會超過real呢?我們現(xiàn)在絕大多數(shù)服務(wù)器都是多cpu或者多核的,當(dāng)多個線程操作時,user和sys會疊加這些cpu時間,所以看到user或者sys的時間超過real是很正常的。

啰里吧嗦介紹完gc日志后,接下來我們就可以進(jìn)入正題,看一下JVM對象內(nèi)存的分配策略。

內(nèi)存分配策略

對象內(nèi)存的分配,簡單點兒說,就是在heap上分配內(nèi)存(JIT編譯可能間接在棧上分配),對象首先會在Eden區(qū)分配,當(dāng)然,如果啟動了本地線程分配緩存,則優(yōu)先在線程的TLAB上分配,同時,也會有少數(shù)情況會在old區(qū)分配,具體的分配細(xì)節(jié)跟垃圾收集器以及JVM內(nèi)存相關(guān)參數(shù)相關(guān)。接下來就根據(jù)實例具體分析一下相關(guān)分配策略。

1. 對象首先在Eden區(qū)分配

在大多數(shù)情況下,對象都優(yōu)先在eden區(qū)分配內(nèi)存,當(dāng)eden區(qū)內(nèi)存空間不夠時,JVM會發(fā)起一次Monitor GC(對young區(qū)的gc)

案例1

在案例1中,通過JVM參數(shù)-Xms20M -Xmx20M -Xmn10M限制了heap的大小為20M并且不可擴展,young區(qū)的大小為10M,剩下的10M給old區(qū),在main方法中,創(chuàng)建byte1到btye4四個對象,一共10M,我們看一下會發(fā)生什么?
案例1.gc日志

從gc日志可以看出:

  1. 在分配bytes1到bytes3后,eden區(qū)沒有額外的空間,再創(chuàng)建bytes4的時候,此時eden區(qū)內(nèi)存不夠,觸發(fā)Minitor GC,本次gc結(jié)束后,yong區(qū)的6441k變成872k,但是由于bytes1到bytes3對象都是存活的,所以總得內(nèi)存量其實并沒有減少;

  2. 在發(fā)生Monitor GC的過程中,由于Survivor空間只有1M,不足以放下bytes1到bytes3的任何一個對象,此時,通過分配擔(dān)保機制,會提前進(jìn)入old區(qū);

  3. 這次GC結(jié)束后,bytes4被順利分配在eden區(qū),此時,eden區(qū)占用6M,Survivor空閑,old區(qū)被占用4M。

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

:大對象定義:所謂的大對象,其實就是指需要大量的連續(xù)內(nèi)存空間的對象,比如長度很長的數(shù)組。

對于JVM來說,需要分配大對象是一個壞消息,如果程序中經(jīng)常出現(xiàn)大對象就容易導(dǎo)致gc的提前觸發(fā)。當(dāng)然,JVM提供參數(shù)-XX:PretenureSizeThreshold,一旦對象所需內(nèi)存大小大于該參數(shù)配置的閾值,直接在old區(qū)為其分配內(nèi)存空間。當(dāng)然,這樣做的目的一則是為了避免gc提前出發(fā),二則是為了避免在eden區(qū)和Survivor區(qū)發(fā)生大量的內(nèi)存拷貝。接下來還是以一個簡單的例子驗證該規(guī)則。

案例2

在案例2中,通過參數(shù)-XX:PretenureSizeThreshold=4194304設(shè)置閾值為4M,一旦待分配對象大小超過4M,直接在old區(qū)進(jìn)行內(nèi)存分配。在main方法中,要創(chuàng)建一個大小為5M的byte數(shù)組,我們來看下gc日志,看看這個對象是不是直接在old區(qū)分配內(nèi)存的。
案例2.gc日志

從gc日志標(biāo)紅的地方可以看出,byte4確實直接被放在了old區(qū)。

:需要注意的是,-XX:PretenureSizeThreshold只對ParNew和Serial垃圾收集器有效,如果你需要使用該參數(shù)的話,可以使用ParNew + CMS。

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

JVM采用分代收集來管理內(nèi)存,為了在gc的時候能夠確認(rèn)哪些對象要放在young區(qū),哪些對象放在old區(qū),JVM為給每一個對象都定義了一個年齡計數(shù)器,如果對象在eden區(qū)被分配內(nèi)存并且經(jīng)過第一次Monitor GC后還存活,此時該對象會被移動到Survivor區(qū),對象的年齡被設(shè)置成為1,該對象在Survivor區(qū)中每經(jīng)過一次Monitor GC還不被回收,年齡就加1,當(dāng)它的年齡增加達(dá)到一定的值時(默認(rèn)值是15),對象就會被晉升到old區(qū)。當(dāng)然,JVM提供參數(shù)-XX:MaxTenuringThreshold來設(shè)置對象晉升到old區(qū)的年齡閾值。

案例3

在案例3中,通過-XX:MaxTenuringThreshold=1設(shè)置對象晉升到old區(qū)的年齡閾值為1,那么,gc結(jié)束后,bytes1和bytes2均會進(jìn)入old區(qū),我們看一下gc日志看看是不是這樣。
案例3.gc日志

從gc日志可以看出,經(jīng)過兩次Monitor GC,bytes1和bytes2均進(jìn)入old區(qū),bytes3在eden區(qū)。

4. 動態(tài)年齡判定

雖然JVM要求對象年齡必須要達(dá)到-XX:MaxTenuringThreshold設(shè)置的閾值才能晉升到old區(qū),但是,為了更好的適應(yīng)不同的內(nèi)存使用情況,JVM增加了一個新的晉升到old區(qū)的條件:如果在Survivor區(qū)中相同年齡的對象所占內(nèi)存空間大于Survivor區(qū)的一半,不小于該年齡的對象可以直接進(jìn)入old區(qū),不需要達(dá)到-XX:MaxTenuringThreshold設(shè)置的閾值。

案例4

gc日志:
案例4.gc日志

從gc日志可以看出,經(jīng)過兩次Monitor GC,由于bytes1所占用內(nèi)存空間大于Survivor區(qū)的一半,bytes1和bytes2均進(jìn)入old區(qū)。

5. 空間分配擔(dān)保

在發(fā)生Monitor GC之前,JVM會檢查old區(qū)的最大可用連續(xù)空間是否大于young區(qū)所有對象總空間,如果條件成立,那么Monitor GC一定是安全的,進(jìn)行一次Monitor GC,如果不成立:

  • 在jdk6 update 24之前JVM則會讀取參數(shù)-XX:-HandlePromotionFailure值判斷是否允許擔(dān)保失敗,如果允許,檢查old區(qū)的最大可用連續(xù)空間是否大于晉升到old區(qū)對象的平均大小,如果大于,再進(jìn)行一次Monitor GC,如果小于或者不允許擔(dān)保失敗,則進(jìn)行一次Full GC;

  • 在jdk6 update 24之后,雖然JVM還定義參數(shù)-XX:-HandlePromotionFailure,但是已經(jīng)不會再使用它,規(guī)則變?yōu)橹灰猳ld區(qū)最大可用連續(xù)空間大于晉升到old區(qū)的對象的平均大小,就進(jìn)行一次Monitor GC,否則,進(jìn)行一次Full GC,JVM源碼也可以驗證此規(guī)則:

    JVM源碼

看到這里大家估計還是有點懵,還是不理解為什么要空間分配擔(dān)保,接下來就解釋下為什么需要空間分配擔(dān)保。

為什么需要空間分配擔(dān)保?

注:空間分配擔(dān)保其實就是JVM確認(rèn)old區(qū)是否可以容納Monitor GC后晉升到old區(qū)的對象們。

由于young區(qū)的gc算法是復(fù)制收集算法,為了內(nèi)存的使用率,JVM只使用其中的一個Survivor取作為中間轉(zhuǎn)換空間,當(dāng)出現(xiàn)大量對象在Monitor GC后還存活的,此時,Survivor區(qū)無法容納的對象直接進(jìn)入old區(qū),在進(jìn)入old區(qū)之前JVM一定要確認(rèn)old區(qū)是否有足夠的剩余空間可以容納這些對象。由于在回收之前并不知道有多少對象要進(jìn)入old區(qū),JVM設(shè)計者認(rèn)為可以取每一次Monitor GC晉升到old區(qū)的對象容量的平均大小為經(jīng)驗值,將該經(jīng)驗值與old區(qū)剩余空間比較,來決定是否要進(jìn)行一次Full GC讓old區(qū)釋放出更多的空間。但是,取平均值畢竟是一種動態(tài)概率手段,如果某一次Monitor GC后存活對象陡增,遠(yuǎn)遠(yuǎn)高于平均值,此時還是會擔(dān)保失敗,一旦出現(xiàn)擔(dān)保失敗,JVM會發(fā)起一次Full GC。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,006評論 2 374

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