[TOC]
GC日志閱讀
在開(kāi)發(fā)的世界里揪漩,閱讀日志是最基礎(chǔ)的能力,也是解決問(wèn)題重要的工具惋耙。同樣閱讀gc日志也是解決虛擬機(jī)內(nèi)存的基礎(chǔ)技能虽缕,通過(guò)配置參數(shù)-XX:+PrintGCDetails就可以打印gc日志,建議加上參數(shù)-Xloggc指定gc日志目錄,避免gc日志和console控制臺(tái)日志混亂造成的閱讀困難女淑。
每一種收集器的日志都會(huì)略有不同瞭郑,但會(huì)維持一定的共性,以下面一段日志為例:
0.332: [GC (Allocation Failure) [PSYoungGen: 6120K->504K(6144K)] 12535K->12549K(19968K), 0.0066909 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.339: [Full GC (Ergonomics) [PSYoungGen: 504K->0K(6144K)] [ParOldGen: 12045K->10615K(13824K)] 12549K->10615K(19968K), [Metaspace: 3473K->3473K(1056768K)], 0.1372999 secs] [Times: user=0.28 sys=0.00, real=0.14 secs]
最前面的0.332和0.339代表了gc的發(fā)生的時(shí)間鸭你,它的含義是表達(dá)虛擬機(jī)啟動(dòng)到發(fā)生gc的秒數(shù)屈张。后面的GC和Full GC代表著垃圾收集的停頓類(lèi)型,如果是GC代表的是新生代的GC袱巨,也稱(chēng)ygc和minor gc阁谆,fullgc代表的是對(duì)整堆的一個(gè)gc。后面括號(hào)里的Allocation Failure和Ergonomics代表的是發(fā)生gc的原因愉老,分別是eden區(qū)域空間不夠和parOldGen空間不夠?qū)е碌膅c和fullgc問(wèn)題场绿。以Full GC為例,接下來(lái)的[PSYoungGen嫉入、[ParOldGen焰盗、[Metaspace代表gc發(fā)生的區(qū)域,分別是年輕代劝贸、老年代姨谷、元空間,其名字也是由所使用的gc收集器密切相關(guān)映九,大致如下:
收集器 顯示區(qū)域
serial DefNew
ParNew ParNew
Parallel Scavenge PSYoungGen
serial old Tenured
parallel old ParOldGen
CMS CMS
后面方括號(hào)內(nèi)部的 504K->0K(6144K)代表著該區(qū)域GC前使用容量-》GC后該區(qū)域所使用容量(該區(qū)域總?cè)萘浚┟蜗妫嚼ㄌ?hào)之外的12549K->10615K(19968K)則代表gc之前堆中使用容量-》gc后堆中使用容量(堆總?cè)萘浚?.1372999 secs這個(gè)很簡(jiǎn)單,代表gc占用時(shí)間件甥,單位是秒捌议。
內(nèi)存分配與回收策略
- 對(duì)象優(yōu)先在Eden分配
大多數(shù)情況下,對(duì)象優(yōu)先在新生代Eden區(qū)中分配引有。當(dāng)Eden區(qū)域沒(méi)有足夠空間進(jìn)行分配時(shí)瓣颅,將發(fā)生一次Minor GC。虛擬機(jī)提供了-XX:+PrintGCDetails用來(lái)輸出gc日志譬正,此日志會(huì)告訴我們垃圾收集行為時(shí)的內(nèi)存日志宫补,并在進(jìn)程結(jié)束后輸出當(dāng)前內(nèi)存各區(qū)域的分配情況。上例子:
public class TestAllocation {
private static final int _1MB=1024*1024;
public static void main(String[] args) {
byte[] a1,a2,a3,a4;
a1=new byte[2*_1MB];
a2=new byte[2*_1MB];
a3=new byte[2*_1MB];
a4=new byte[4*_1MB];
}
}
gc日志如下所示:
"C:\Program Files\Java\jdk1.8.0_151\bin\java" -XX:+PrintGCDetails -Xmx20m -Xms20m -Xmn10m
[GC (Allocation Failure) [PSYoungGen: 6294K->808K(9216K)] 6294K->4912K(19456K), 0.0023349 secs] [Times: user=0.09 sys=0.00, real=0.02 secs]
Heap
PSYoungGen total 9216K, used 7273K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 78% used [0x00000000ff600000,0x00000000ffc50670,0x00000000ffe00000)
from space 1024K, 78% used [0x00000000ffe00000,0x00000000ffeca020,0x00000000fff00000)
to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
ParOldGen total 10240K, used 4104K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
object space 10240K, 40% used [0x00000000fec00000,0x00000000ff002020,0x00000000ff600000)
Metaspace used 3473K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 381K, capacity 388K, committed 512K, reserved 1048576K
其中vm配置參數(shù)從第一行便可知曾我。新生代分為eden區(qū)域和兩塊survior區(qū)域粉怕,默認(rèn)比例為8:1:1,從圖中eden:from:to=8192:1024:1024可以得到驗(yàn)證抒巢。下來(lái)我們就根據(jù)gc日志分析下在執(zhí)行這段程序時(shí)贫贝,jvm究竟都做了哪些事。
從這張圖片可知,在jdk1.8.0_151中稚晚,即使跑一個(gè)空的main函數(shù)崇堵,新生代就要占2362k,這個(gè)是虛擬機(jī)的初始內(nèi)存占用客燕,好奇寶寶可以通過(guò)jmap命令看看到底堆里裝了什么≡Ю停現(xiàn)在讓我們回到最開(kāi)始代碼中,會(huì)將a1幸逆、a2分配在新生代的eden區(qū)棍辕,此時(shí)eden區(qū)域?yàn)?048k+2048K+2362k=6458k,因?yàn)閑den區(qū)域空間不夠,不足以將a3裝入还绘,此時(shí)觸發(fā)minor gc,又因?yàn)榇藭r(shí)a1楚昭、a2對(duì)象還存活,suivor區(qū)域只有1024k拍顷,故將a1抚太、a2分配擔(dān)保到老年代。從日志中可知昔案,經(jīng)歷過(guò)一次minor gc新生代還有808k的存活對(duì)象尿贫,因?yàn)閍1、a2已經(jīng)擔(dān)保到老年代踏揣,故這是初始內(nèi)存中經(jīng)過(guò)gc存活的對(duì)象庆亡,通過(guò)復(fù)制算法轉(zhuǎn)移到survivor中。此時(shí)eden區(qū)域是0k捞稿,其中一塊survivor是初始內(nèi)存又谋,老年代存放著a1、a2對(duì)象娱局,此時(shí)開(kāi)始繼續(xù)分配對(duì)象內(nèi)存彰亥,因a3+a4<eden區(qū)域,故全部分配在eden區(qū)域衰齐。
- 大對(duì)象直接進(jìn)入老年代
哪怕你從來(lái)沒(méi)有學(xué)習(xí)過(guò)jvm知識(shí)任斋,你或許也聽(tīng)說(shuō)過(guò)江湖上流傳著大對(duì)象直接進(jìn)入老年代這個(gè)傳聞。很多人都知道這個(gè)知識(shí)點(diǎn)耻涛,但恐怕大多數(shù)人并不能準(zhǔn)確的去描述這個(gè)分配策略废酷。
1.何謂大對(duì)象?
所謂大對(duì)象就是需要大量連續(xù)內(nèi)存空間的對(duì)象抹缕,上個(gè)例子中的byte數(shù)組就是典型的大對(duì)象澈蟆。
2.參數(shù)-XX:PretenureSizeThreshold的作用?
虛擬機(jī)提供了-XX:PretenureSizeThreshold歉嗓,令大于這個(gè)值得對(duì)象直接在老年代分配。hotspot可以在年輕代手機(jī)內(nèi)存的收集器有Serial背蟆、ParNew鉴分、Parallel Scavenge以及G1(G1劃分內(nèi)存區(qū)域比較特殊暫不考慮)哮幢。其中只有Serial和ParNew收集器可以識(shí)別這個(gè)參數(shù),Parallel Scavenge是不識(shí)別這個(gè)參數(shù)的志珍,但并不是大對(duì)象直接進(jìn)入老年代分配策略對(duì)其就是無(wú)效的橙垢,在Parallel Scavenge中自有它的實(shí)現(xiàn),大約等于Eden區(qū)域一半的對(duì)象會(huì)被認(rèn)成大對(duì)象伦糯。感興趣的可以來(lái)這看看柜某,傳送門(mén):鏈接描述.如果想要使用-XX:PretenureSizeThreshold參數(shù),可以考慮使用ParNew+CMS的組合敛纲。給大家展示一個(gè)例子:
public class BigObject {
public static void main(String[] args) {
byte[] test = new byte[4*1024*1024];
}
}
從gc日志喂击,我們很容易得出大對(duì)象進(jìn)入老年代這個(gè)結(jié)論。對(duì)了淤翔,還需要注意的是XX:PretenureSizeThreshold的單位是k翰绊,不能像-Xmx3mb這樣直接指定。
3.為什么大對(duì)象要進(jìn)入老年代旁壮?
在搞清楚這個(gè)問(wèn)題之前监嗜,我們首先要去揣摩大師們?cè)O(shè)計(jì)分代算法的意圖。設(shè)計(jì)師們希望新生代的對(duì)象多數(shù)是朝生夕滅的抡谐,故新生代采用復(fù)制算法最合適裁奇。復(fù)制算法的優(yōu)點(diǎn)是簡(jiǎn)單,速度快(在存活對(duì)象少的情況下)麦撵,缺點(diǎn)是占內(nèi)存要發(fā)生內(nèi)存復(fù)制刽肠。這樣做的目的就是避免在eden區(qū)和兩個(gè)survivor區(qū)之間發(fā)生大量的內(nèi)存復(fù)制。
長(zhǎng)期存活的對(duì)象將進(jìn)入老年代
虛擬機(jī)給每個(gè)對(duì)象定義了一個(gè)對(duì)象年齡計(jì)數(shù)器厦坛,保存在對(duì)象頭中的Mark word部分五垮。如果對(duì)象在eden出生,經(jīng)歷過(guò)一次minor gc仍然活著杜秸,并且能被survivor區(qū)容納放仗,將會(huì)被移動(dòng)到survivor區(qū)域,并且將gc年齡設(shè)置為1撬碟,這種對(duì)象沒(méi)經(jīng)歷一次Minor gc 诞挨,年齡就增加一歲,當(dāng)它的年齡增加到一定程度(默認(rèn)是15歲)呢蛤,就會(huì)被晉升到老年代惶傻,這個(gè)年齡閾值可以通過(guò)參數(shù)-XX:MaxTenuringThresold來(lái)設(shè)置.動(dòng)態(tài)對(duì)象年齡判定
虛擬機(jī)并不是永遠(yuǎn)要求對(duì)象的年齡永遠(yuǎn)達(dá)到MaxTenuringThresold才能晉升老年代,如果在survivor空間中相同年齡所有的對(duì)象的大小總和大于survivor空間的一半其障,年齡大于或等于改年齡的對(duì)象直接進(jìn)入老年代银室,無(wú)須等到MaxTenuringThresold要求的年齡。空間分配擔(dān)保
在發(fā)生minor gc之間,虛擬機(jī)會(huì)先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對(duì)象總空間蜈敢,如果條件成立辜荠,則代表這次gc是安全的。如果不成立抓狭,jdk1.7和jdk1.8的版本中會(huì)繼續(xù)檢查老年代最大的可用空間是否大于歷次晉升到老年代對(duì)象的平均大小伯病,如果小于則進(jìn)行一次full gc,如果大于則嘗試進(jìn)行一次minor gc,如果出現(xiàn)老年代擔(dān)保失敗的情況則會(huì)進(jìn)行一次full gc否过。