本文分析基于Serial贸典,cms考赛,parnew一罩,等經(jīng)典分代垃圾收集器月趟。
對JVM內(nèi)存的系統(tǒng)級的調(diào)優(yōu)主要的目的是減少GC的頻率和Full GC的次數(shù)灯蝴。導(dǎo)致Full GC的原因一般是老年代、元空間 不足孝宗。
先了解對象的空間分配機制绽乔,也是調(diào)優(yōu)的基礎(chǔ)
1. 對象優(yōu)先在Eden分配
Eden 內(nèi)存不夠用會出發(fā)Minor GC
可能會有99%以上的被回收掉,剩余存活的被挪到為空的那塊survivor區(qū)碳褒,下一次eden區(qū)滿了后又會觸發(fā)minor gc折砸,把eden區(qū)和survivor區(qū)垃圾回收,把存活的對象一次性挪到另外一塊為空的survivor區(qū)(survivor存不下會直接存入老年代)沙峻。因為新生代的對象都是朝生夕死的睦授,存活時間很短,所以JVM默認的8:1:1的比例是很合適的摔寨,讓eden區(qū)盡量的大去枷,survivor區(qū)夠用即可。
2. 長期存活的對象進入老年代
對象頭中存儲對象年齡是复,
對象在Eden誕生删顶,經(jīng)過第一次MinorGC后依然存活,且能被survivor 容納淑廊,會被移動到survivor 并且年齡為1逗余。對象在survivor中每熬過一次MinorGC年齡就加1.
到達一定年齡(默認15,cms默認6)進入老年代季惩。不能被survivor容納录粱,直接入老年代。
如果 -XX:MaxTenuringThreshold=1画拾,則在第二次MinorGC時啥繁,進入老年代
3. 大對象直接進入老年代配置
參數(shù) -XX:pretenureSizeThreshold=1000 (單位是字節(jié)) 控制對象空間大于指定大小 直接進入老年代,需要在新生代使用對應(yīng)的垃圾收集器青抛。
以下兩種都可以支持旗闽。
-XX:+UseSerialGc
-XX:+UseParNew
這樣設(shè)置可避免大對象在Eden和兩個s區(qū)來回復(fù)制,產(chǎn)生大量的內(nèi)存復(fù)制操作蜜另。
4. 動態(tài)對象年齡判定
survivor 中 相同年齡的對象總和适室,大于survivor空間的一半,大于等于該年齡的對象可以直接進入老年代蚕钦。無需等待到閾值年齡亭病。
5. 空間分配擔保
發(fā)生MinorGC 之前,虛擬機需要先檢查老年代最大可用連續(xù)空間嘶居,是否大于新生代所有對象總空間。如果大于,執(zhí)行MinorGC, 否則看是否設(shè)置了允許擔保邮屁。
如果允許擔保整袁,則判斷老年代最大可用連續(xù)空間是否大于 以往晉升到老年代對象的平均值, 如果大于佑吝,進行MinorGC, 如果小于坐昙,或者未設(shè)置擔保,先進行Full Gc.
擔保內(nèi)容:Minor Gc之后大量對象依然存活芋忿,需要把無法存入survivor的對象直接送入老年代炸客。
調(diào)優(yōu)步驟
1. jinfo 查詢 jvm參數(shù),明確各區(qū)域內(nèi)存空間大小
jinfo -flags pid
2. 明確年輕代對象增長的速率--》確定Eden 多久被放滿
可以執(zhí)行命令 jstat -gc pid 1000 10 (每隔1秒執(zhí)行1次命令戈钢,共執(zhí)行10次痹仙,打印堆、元空間的容量殉了、已用空間开仰,gc耗時等信息),通過觀察EU(eden區(qū)的已使用)來估算每秒eden大概新增多少對象薪铜,如果系統(tǒng)負載不高众弓,可以把頻率1秒換成1分鐘,甚至10分鐘來觀察整體情況隔箍。注意谓娃,一般系統(tǒng)可能有高峰期和日常期,所以需要在不同的時間分別估算不同情況下對象增長速率蜒滩。
假設(shè) 每秒5M傻粘, 100s 放滿Eden 區(qū),那么100s 進行一次youngGC帮掉,如需放大年輕代gc周期弦悉,可以適當增大年輕代。并且考慮高峰期時蟆炊,業(yè)務(wù)量情況稽莉。
3. 明確Young GC的觸發(fā)頻率和每次耗時
知道年輕代對象增長速率,就能推根據(jù)eden區(qū)的大小推算出Young GC大概多久觸發(fā)一次涩搓,Young GC的平均耗時可以通過 YGCT/YGC大致計算得出污秆,根據(jù)結(jié)果大概就能知道系統(tǒng)大概多久會因為Young GC的執(zhí)行而卡頓多久。
4. 明確老年代對象增長速率
之前已經(jīng)大概知道Young GC的頻率昧甘,假設(shè)是每5分鐘一次良拼,那么可以執(zhí)行命令 jstat -gc pid 300000 10 ,300000 即5分鐘的毫秒數(shù)充边,觀察每次結(jié)果eden庸推,survivor和老年代使用的變化情況常侦,在每次gc后eden區(qū)使用一般會大幅減少,survivor和老年代都有可能增長贬媒,這些增長的對象就是每次Young GC后存活的對象聋亡,同時還可以看出每次Young GC后進去老年代大概多少對象,從而可以推算出老年代對象增長速率际乘。
需要考慮業(yè)務(wù)場景高峰期時坡倔,對象增長率突增。
5. Full GC的觸發(fā)頻率和每次耗時
知道了老年代對象的增長速率就可以推算出Full GC的觸發(fā)頻率了脖含,F(xiàn)ull GC的每次耗時可以用公式 FGCT/FGC 計算得出罪塔。根據(jù)業(yè)務(wù)需要調(diào)整老年代大小⊙控制full gc 頻率征堪。
6. 也根據(jù)業(yè)務(wù)場景分析每秒產(chǎn)生對象大小
假設(shè)每秒 500 個訂單的系統(tǒng),每個訂單對象 大概占用空間多大(根據(jù)字段屬性大致計算)港柜。假設(shè)每個對象1kb, 每秒 0.5M请契, 由于系統(tǒng)還有 查詢服務(wù)等其他對象。預(yù)估每秒對象占用空間 0.5 * 10 = 5M夏醉,如果 設(shè)置新生代 500M,比例 8:1:1爽锥,那么Eden 400M,80s Eden 放滿畔柔,進行young GC氯夷。
最終目標
朝生夕死的對象,盡可能在 young GC 后回收靶擦,存活較久的對象盡早進入老年代腮考。降低Full GC 、young GC 頻率, 維持穩(wěn)定的gc頻率玄捕。根據(jù)GC 耗時踩蔚,業(yè)務(wù)系統(tǒng)允許停頓時間,最終確定 堆空間大小和布局比例枚粘,進行驗證馅闽,反復(fù)調(diào)整。
特殊情況
如果fullgc 頻率較高馍迄,需要考慮以下幾種情況:
- 是否觸發(fā)了空間擔保機制 --- 設(shè)置允許擔保(默認允許)
- 是否因為survivor 太小導(dǎo)致對象過早進入老年代 --- 需要調(diào)整 增大 survivor
- 是否在代碼中調(diào)用GC -- 通過參數(shù)禁止
- 元空間不足 -- 會自動擴展福也,但還是設(shè)置合適較好。
如果young GC 頻繁攀圈,需要調(diào)整增大新生代暴凑,一般設(shè)置固定大小,不允許擴展赘来。具體大小根據(jù)業(yè)務(wù)量計算现喳。
嘗試之后GC還是很頻繁凯傲,考慮使用jmap堆中哪些對象占用空間較大,配合top 拿穴、jstack查看哪個線程在頻繁創(chuàng)建該對象泣洞。根據(jù)代碼分析是否存在問題.
jmap -histo [pid] 查看內(nèi)存信息忧风,實例個數(shù)以及占用內(nèi)存大小
配合top -Hp pid 查詢內(nèi)存占用較高的線程默色,在使用jstack 命令
1,使用命令top -p <pid> 狮腿,顯示你的java進程的內(nèi)存情況腿宰,pid是你的java進程號,比如19663
2缘厢,按H吃度,獲取每個線程的內(nèi)存情況
3,找到內(nèi)存和cpu占用最高的線程tid贴硫,比如19664
4椿每,19664轉(zhuǎn)為十六進制得到 0x4cd0,此為線程id的十六進制表示
5英遭,執(zhí)行 jstack 19663|grep -A 10 4cd0间护,得到線程堆棧信息中 4cd0 這個線程所在行的后面10行,從堆棧中可以發(fā)現(xiàn)
6挖诸,查看對應(yīng)的堆棧信息找出可能存在問題的代碼
安全點導(dǎo)致的長時間停頓
根據(jù)gc日志觀察用戶線程停頓耗時汁尺,和垃圾收集耗時。差別較大時多律,可能存在安全點超時痴突。
gc需要所有用戶線程到達安全點才能開始。
代碼中出現(xiàn)的循環(huán)可能會導(dǎo)致安全點超時狼荞。
循環(huán)中的變量如果是int,jvm認為是可數(shù)循環(huán)辽装,可能不會在循還末尾設(shè)置安全點。中間如果存在耗時操作則會一直等待相味∈盎可以修改為long 型,jvm判定為不可數(shù)循環(huán)攻走。會在每次循環(huán)末尾設(shè)置安全點殷勘。也可以設(shè)置參數(shù)-XX:+UseCountedLoopSafepoints 強制可數(shù)循環(huán)中也防止安全點。這個參數(shù) jdk1.8部分版本存在bug需要注意昔搂。
設(shè)置虛擬機安全點等待時間玲销。超時2000ms后會自動打印線程名稱 方便問題排查。
-XX:+SafepointTimeout
-XX:SafepointTimeoutDelay=2000