Java虛擬機(jī)內(nèi)存分配與回收策略

Java虛擬機(jī)中的內(nèi)存分配與回收策略就是 Java的自動內(nèi)存管理焰望,其最核心的部分就是內(nèi)存中對象的分配與回收。所以在了解虛擬機(jī)內(nèi)存分配與回收策略之前我們有必要了解一下Java堆內(nèi)存的組成部分寨腔。

堆內(nèi)存示意圖

從上圖可以得知,堆內(nèi)存主要分為新生代、老年代环凿、永久代幾部分組成集嵌,其中新生代又分為一個Eden區(qū)和兩個Survivor區(qū)萝挤,其比例為8:1。JDK1.8之后根欧,用元空間(Metaspace)的區(qū)域取代了堆中的永久代區(qū)域(永久代使用的是JVM的堆內(nèi)存空間平斩,而元空間使用的是物理內(nèi)存,直接受到本機(jī)的物理內(nèi)存限制)咽块。


  • 對象優(yōu)先在Eden分配

大多數(shù)情況下绘面,對象在新生代Eden區(qū)中分配。當(dāng)Eden區(qū)中沒有足夠空間進(jìn)行分配時 ,虛擬機(jī)將發(fā)起一次Minor GC揭璃。代碼測試如下:

/*虛擬機(jī)參數(shù)配置如下 : -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:+PrintGCDetails  */
public class MemoryAllocate {

    public static void main(String[] args) {
       
    }
}

首先解釋下 虛擬機(jī)參數(shù)配置 : -Xms20M -Xmx20M -Xmn10M 三個參數(shù)限制了Java堆大小為20MB晚凿,不可擴(kuò)展(-Xms設(shè)置堆容量的最小值,-Xmx設(shè)置堆容量的最大值)瘦馍,其中10M分配給新生代(-Xmn設(shè)置對容量新生代的大屑呋唷),剩下的10M分配給老年代情组;-XX:SurvivorRatio=8 決定了新生代中Eden區(qū)與一個Survivor區(qū)的空間比例為8:1燥筷;-XX:+PrintGCDetails 打印內(nèi)存回收日志。接下來看一下運(yùn)行結(jié)果:

Heap
 PSYoungGen      total 9216K, used 2148K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 26% used [0x00000000ff600000,0x00000000ff819270,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
  to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
 ParOldGen       total 10240K, used 0K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 0% used [0x00000000fec00000,0x00000000fec00000,0x00000000ff600000)
 Metaspace       used 3143K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 342K, capacity 388K, committed 512K, reserved 1048576K

從結(jié)果中分析得知新生代(PSYoungGen)總可用空間為Eden區(qū)與一個Survivor(from 與to其中)區(qū)的總和:(8192+1024)這里說的是新生代的可用空間院崇,而不是總空間肆氓,總空間大小為Eden與兩個Survivor區(qū)的和;老年代(ParOldGen) 的空間大小為10240k底瓣;Metaspace(元空間,JDK1.8之后用于取代永久代的空間)谢揪。因此虛擬機(jī)參數(shù)配置已經(jīng)生效,另外捐凭,雖然我們什么都沒做拨扶,Eden區(qū)的空間也已經(jīng)被使用26%。接下來看看第二段代碼:

public class MemoryAllocate {

    public static void main(String[] args) {
        //連續(xù)向堆中申請5個1M的空間
        byte[] allocate1 = new byte[1 * 1024 * 1024];
        byte[] allocate2 = new byte[1 * 1024 * 1024];
        byte[] allocate3 = new byte[1 * 1024 * 1024];
        byte[] allocate4 = new byte[1 * 1024 * 1024];
        byte[] allocate5 = new byte[1 * 1024 * 1024];
    }
}

輸出結(jié)果如下 : 
Heap
 PSYoungGen      total 9216K, used 7598K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 92% used [0x00000000ff600000,0x00000000ffd6bab8,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
  to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
 ParOldGen       total 10240K, used 0K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 0% used [0x00000000fec00000,0x00000000fec00000,0x00000000ff600000)
 Metaspace       used 3231K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 352K, capacity 388K, committed 512K, reserved 1048576K

PSYongGen的使用空間從2148k增加到7598k,而兩個Survivor區(qū)根本沒有使用茁肠,所以新增加的5450K空間全部在Eden中患民,證實(shí)了對象優(yōu)先在Eden區(qū)中分配的觀點(diǎn)。接下來看第三段代碼:

public class MemoryAllocate {

    public static void main(String[] args) {
        //連續(xù)向堆中申請5個1M的空間
        byte[] allocate1 = new byte[1 * 1024 * 1024];
        byte[] allocate2 = new byte[1 * 1024 * 1024];
        byte[] allocate3 = new byte[1 * 1024 * 1024];
        byte[] allocate4 = new byte[1 * 1024 * 1024];
        byte[] allocate5 = new byte[1 * 1024 * 1024];
        //再申請1一個1M的空間
        byte[] allocate6 = new byte[1 * 1024 * 1024];
    }
}

輸出結(jié)果如下:
[GC (Allocation Failure) [PSYoungGen: 7270K->968K(9216K)] 7270K->6096K(19456K), 0.0035282 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 968K->0K(9216K)] [ParOldGen: 5128K->5873K(10240K)] 6096K->5873K(19456K), [Metaspace: 3218K->3218K(1056768K)], 0.0060934 secs] [Times: user=0.08 sys=0.02, real=0.01 secs] 
Heap
 PSYoungGen      total 9216K, used 1353K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 16% used [0x00000000ff600000,0x00000000ff7527c8,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 10240K, used 5873K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 57% used [0x00000000fec00000,0x00000000ff1bc6f8,0x00000000ff600000)
 Metaspace       used 3232K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 352K, capacity 388K, committed 512K, reserved 1048576K

其中觸發(fā)了一次GC回收和一次Full GC:

  • [PSYoungGen: 7270K->968K(9216K)] 表示GC前年輕代占用內(nèi)存7270K垦梆,GC后占用內(nèi)存968K酒奶,內(nèi)>存區(qū)域總?cè)萘?M;
  • 7270K->6096K(19456K) 表示GC前堆占用內(nèi)存7270K奶赔,GC后占用內(nèi)存6096K惋嚎,堆總?cè)萘?0M;
  • [Full GC (Ergonomics) [PSYoungGen: 968K->0K(9216K)] 表示進(jìn)行了一次Full GC,后面會說到Full GC與GC的區(qū)別站刑。
    接下來看結(jié)果:
    從結(jié)果中我們可以得知ParOldGen老年代中使用了5873K的內(nèi)存另伍,而eden區(qū)中的內(nèi)存使用情況反而變小了。因?yàn)楫?dāng)給allocate6分配內(nèi)存的時候绞旅,eden區(qū)中的的剩余空間已經(jīng)不足分配allocate6所需的1M內(nèi)存摆尝, 因此發(fā)生GC,而GC期間發(fā)現(xiàn)已有的5個1M大小的對象無法全部放入Survivor空間,所以只好通過擔(dān)保分配機(jī)制將這五個對象提前轉(zhuǎn)移到老年代中因悲。

注:上面代碼運(yùn)行可能會產(chǎn)生不一樣的結(jié)果堕汞,那就需要讀者另行分析了。

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

所謂的大對象主要指晃琳,需要大量連續(xù)內(nèi)存空間的Java對象讯检,最典型的例子就是那種很長的字符串以及數(shù)組琐鲁。虛擬機(jī)提供了-XX:PretenureSizeThreshold參數(shù),令大于這個設(shè)置值的對象直接在老年代分配人灼。這樣做的好處是避免了在Eden區(qū)和兩個Survivor區(qū)之間發(fā)生大量的內(nèi)存復(fù)制(新生代采用復(fù)制算法回收內(nèi)存)围段。

/*-XX:PretenureSizeThreshold=3145728 虛擬機(jī)參數(shù)配置*/
public class MemoryAllocate {
    
    public static void main(String[] args) {
        //申請一個8M的空間
        byte[] allocate = new byte[8 * 1024 * 1024];
    }
}
輸出結(jié)果 :
Heap
 PSYoungGen      total 9216K, used 2478K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 30% used [0x00000000ff600000,0x00000000ff86b970,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
  to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
 ParOldGen       total 10240K, used 8192K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 80% used [0x00000000fec00000,0x00000000ff400010,0x00000000ff600000)
 Metaspace       used 3231K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 352K, capacity 388K, committed 512K, reserved 1048576K

很明顯的我們可以看到8M的空間全部分配到了老年代之中。這里有一點(diǎn)需要注意的PretenureSizeThreshold參數(shù)只對Serial和ParNew兩款收集器有效投放。本列中使用的Parallel Scavenge收集器奈泪,在所申請的空間大于eden區(qū)可使用的空間時,就會直接將大對象直接分配到老年代灸芳。

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

如果對象在Eden區(qū)出生并且經(jīng)歷過一次Minor GC后仍然存活涝桅,并且能夠被Servivor容納,將被移動到Servivor空間中烙样,并且把對象年齡設(shè)置成為1冯遂。對象在Servivor區(qū)中每熬過一次Minor GC,年齡就增加1歲误阻,當(dāng)它的年齡增加到一定程度(默認(rèn)15歲),就將會被晉級到老年代中晴埂。虛擬機(jī)提供了-XX:MaxTenuringThreshold參數(shù)來設(shè)置這個閾值究反。

  • 動態(tài)對象年齡判定

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

  • 空間分配擔(dān)保

在發(fā)生Minor GC 之前琅锻,虛擬機(jī)會檢查老年代最大可 用的連續(xù)空間是否大于新生代所有對象總空間卦停,如果這個條件成立,那么Minor GC可以確保是安全的恼蓬。如果不成立惊完,則虛擬機(jī)會查看HandlePromotionFailure設(shè)置值是否允許擔(dān)保失敗。如果允許那么會繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于晉級到老年代對象的平均大小处硬,如果大于小槐,將嘗試進(jìn)行一次Minor GC,盡管這次MinorGC 是有風(fēng)險的:如果小于荷辕,或者HandlePromotionFailure設(shè)置不允許冒險凿跳,那這時也要改為進(jìn)行一次Full GC

上面提到了Minor GC依然會有風(fēng)險,是因?yàn)樾律捎脧?fù)制收集算法疮方,假如大量對象在Minor GC后仍然存活(最極端情況為內(nèi)存回收后新生代中所有對象均存活)控嗜,而Survivor空間是比較小的,這時就需要老年代進(jìn)行分配擔(dān)保骡显,把Survivor無法容納的對象放到老年代疆栏。老年代要進(jìn)行空間分配擔(dān)保曾掂,前提是老年代得有足夠空間來容納這些對象,但一共有多少對象在內(nèi)存回收后存活下來是不可預(yù)知的承边,因此只好取之前每次垃圾回收后晉升到老年代的對象大小的平均值作為參考遭殉。使用這個平均值與老年代剩余空間進(jìn)行比較,來決定是否進(jìn)行Full GC來讓老年代騰出更多空間博助。


  • Minor GC 和 Full GC
  1. 新生代GC(Minor GC):指發(fā)生新生代的垃圾收集動作险污,因?yàn)镴ava對象大多數(shù)都具備朝生夕滅的特性,所以Minor GC非常頻繁富岳,一般回收速度也比較快蛔糯。
  2. 老年代GC(Major GC / Full GC): 指發(fā)生在老年代的GC,出現(xiàn)了Major GC窖式,至少會伴隨一次的Minor GC(但非絕對的蚁飒,在Parallel Scavenge收集器的收集策略里就有直接進(jìn)行Major GC的策略選擇過程)。Major GC的速度一般會比Minor GC的速度慢10倍以上萝喘。

上一篇:Java虛擬機(jī)垃圾收集
下一篇:虛擬機(jī)類加載機(jī)制

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末淮逻,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子阁簸,更是在濱河造成了極大的恐慌爬早,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件启妹,死亡現(xiàn)場離奇詭異筛严,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)饶米,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門桨啃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人檬输,你說我怎么就攤上這事照瘾。” “怎么了丧慈?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵网杆,是天一觀的道長。 經(jīng)常有香客問我伊滋,道長碳却,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任笑旺,我火速辦了婚禮昼浦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘筒主。我一直安慰自己关噪,他們只是感情好鸟蟹,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著使兔,像睡著了一般建钥。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上虐沥,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天熊经,我揣著相機(jī)與錄音,去河邊找鬼欲险。 笑死镐依,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的天试。 我是一名探鬼主播槐壳,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼喜每!你這毒婦竟也來了务唐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤带兜,失蹤者是張志新(化名)和其女友劉穎枫笛,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鞋真,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡崇堰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年沃于,在試婚紗的時候發(fā)現(xiàn)自己被綠了涩咖。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡繁莹,死狀恐怖檩互,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情咨演,我是刑警寧澤闸昨,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站薄风,受9級特大地震影響饵较,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜遭赂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一循诉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧撇他,春花似錦茄猫、人聲如沸狈蚤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脆侮。三九已至,卻和暖如春勇劣,著一層夾襖步出監(jiān)牢的瞬間靖避,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工芭毙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留筋蓖,地道東北人。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓退敦,卻偏偏與公主長得像粘咖,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子侈百,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評論 2 344

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