一、業(yè)務(wù)流程簡(jiǎn)圖

二、問(wèn)題分析

1??一個(gè) 4 核 8G 的訂單系統(tǒng),假設(shè)給 JVM 運(yùn)行內(nèi)存為 3 個(gè)G,根據(jù)堆內(nèi)存劃分比例老年代可分 2G,Eden 800M,S0/S1 各 100M。
2??線程運(yùn)行每秒產(chǎn)生 60M 對(duì)象,大概運(yùn)行 13 秒就會(huì)占滿 Eden 區(qū),前 12 秒產(chǎn)生的對(duì)象在做一個(gè) minor gc 后被當(dāng)作垃圾對(duì)象處理掉,第 13 秒產(chǎn)生的對(duì)象不是垃圾對(duì)象,會(huì)被放到 S0 區(qū)。
3??第 13 秒產(chǎn)生的 60M 對(duì)象由于大于 S0 區(qū)的 50% 所以會(huì)被放到老年代。為了能更好地適應(yīng)不同程序的內(nèi)存狀況,HotSpot虛擬機(jī)并不是永遠(yuǎn)要求對(duì)象的年齡必須達(dá)到 -XX:MaxTenuringThreshold 才能晉升老年代,如果在Survivor空間中相同年齡所有對(duì)象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對(duì)象就可以直接進(jìn)入老年代,無(wú)須等到 -XX:MaxTenuringThreshold 中要求的年齡。
因此每隔 13 秒就有 60M 對(duì)象會(huì)被放到老年代,大概 7 到 8 分鐘就會(huì)放滿老年代,老年代放滿后就會(huì)產(chǎn)生一次 full gc,此時(shí)老年代里 99% 的對(duì)象是垃圾對(duì)象,會(huì)被清理掉。而一次 full gc,會(huì)收集整個(gè)堆的垃圾對(duì)象,時(shí)間過(guò)長(zhǎng)。因此系統(tǒng)每隔七八分鐘就會(huì)有持續(xù)性的卡頓現(xiàn)象
三、能否對(duì)JVM調(diào)優(yōu),讓其幾乎不發(fā)生full gc
Full GC 最根本的產(chǎn)生原因就是有對(duì)象不停的進(jìn)入老年代,最后導(dǎo)致空間不足,引發(fā) Full GC。解決思路就是直接破壞掉產(chǎn)生條件,直接減少運(yùn)行時(shí)期間從新生代晉升到老年代的對(duì)象,或者沒(méi)有對(duì)象晉升到老年代就行了。具體措施:
1??大對(duì)象頻繁進(jìn)行老年代,造成老年代空間快速被占滿,造成 Full GC。
解決方案:合理配置-XX:PretenureSizeThreshold大小。避免過(guò)多非必要對(duì)象進(jìn)入老年代。
2??metaspace 空間不足
解決方案:一般這個(gè)里面存放的都是一些 Class 類信息,Class 本身也是一個(gè)對(duì)象,需要空間存放。那么程序代碼中什么時(shí)候會(huì)產(chǎn)生對(duì)象進(jìn)入呢,當(dāng)使用 CGLIB 動(dòng)態(tài)代理不停的生成代理類的時(shí)候,就會(huì)加載到元數(shù)據(jù)空間,當(dāng)然一般 4 核 8G 內(nèi)存的物理機(jī)分配個(gè) 512M 是完全沒(méi)問(wèn)題的。
3??從年輕代晉升到老年代的對(duì)象
- 【長(zhǎng)期存活對(duì)象】達(dá)到了設(shè)置的年齡限制,默認(rèn)是 15 次。
- Young GC 后,存活對(duì)象大于 survivor 區(qū),存活對(duì)象全部進(jìn)入老年代,注意動(dòng)態(tài)年齡的區(qū)別。
- 【動(dòng)態(tài)年齡判定】如果在 Survivor 空間中相同年齡所有對(duì)象大小的總和大于
Survivor 空間的一半,年齡大于或等于該年齡的對(duì)象就可以直接進(jìn)入老年代。
解決方案:
針對(duì)【長(zhǎng)期存活對(duì)象】調(diào)大晉升年齡沒(méi)有多大意義。假設(shè) 10 秒一次 Young GC
15 * 10 = 150s,存活兩分鐘的對(duì)象,可以認(rèn)為是系統(tǒng)中長(zhǎng)期存活的對(duì)象,調(diào)大一點(diǎn),也僅僅是讓它在新生代在多待一會(huì)兒,還不如讓它早點(diǎn)去它該區(qū)的老年區(qū)。存活對(duì)象大于存活區(qū)大小和【動(dòng)態(tài)年齡判定】二者產(chǎn)生原因差不多,都是 Survivor 區(qū)大小分配不合理,可以同時(shí)進(jìn)行優(yōu)化。核心思想就是合理分配新生代老年代內(nèi)存比例大小。

如圖調(diào)整內(nèi)存比例,線程運(yùn)行每秒產(chǎn)生 60M 對(duì)象,大概運(yùn)行 28 秒就會(huì)占滿 Eden 區(qū),此時(shí)前 27 秒產(chǎn)生的對(duì)象在做一個(gè) minor gc 后被當(dāng)作垃圾對(duì)象銷毀掉,第 28 秒產(chǎn)生的對(duì)象會(huì)被放到 S0 區(qū),由于 60M 小于 S0 區(qū)的 50% 不會(huì)被放到老年代。當(dāng) Eden 再一次放滿,此時(shí) minor gc 會(huì)銷毀 Eden 中前 27 秒的垃圾對(duì)象和 S0 中的對(duì)象,Eden 第 28 秒產(chǎn)生的對(duì)象會(huì)被放到 S1 區(qū)。當(dāng) Eden 再一次放滿,minor gc 會(huì)銷毀 Eden 中前 27 秒的垃圾對(duì)象和 S1 中的對(duì)象,Eden 第 28 秒產(chǎn)生的對(duì)象會(huì)被放到 S0 區(qū),如此 JVM 幾乎不發(fā)生 full gc。
四、線上如何去思考內(nèi)存分配
找到最大的壓力瓶頸點(diǎn),也可以說(shuō)并發(fā)最多的點(diǎn)。
根據(jù)系統(tǒng)未來(lái)的業(yè)務(wù)量,訪問(wèn)量,去推算這個(gè)系統(tǒng)每秒的并發(fā)量,注意項(xiàng)目的特殊性,一般可以用二八原則。
計(jì)算一次業(yè)務(wù)新生代會(huì)占用多少內(nèi)存,一般可以考慮
主要業(yè)務(wù)對(duì)象大小擴(kuò)大10~20倍來(lái)預(yù)估,或者直接用工具直接監(jiān)測(cè)。一次業(yè)務(wù)的時(shí)長(zhǎng),即一次請(qǐng)求的耗時(shí)。
根據(jù) 1 和 2 中推算的每秒的并發(fā)量預(yù)估推算對(duì)內(nèi)存空間的占用量,這個(gè)占用量是一秒內(nèi)或者說(shuō)在并發(fā)過(guò)程中無(wú)法回收的。
無(wú)法回收的對(duì)象大小 = 業(yè)務(wù)處理時(shí)間 * QPS * 每個(gè)處理會(huì)產(chǎn)生的對(duì)象大小根據(jù)內(nèi)存的推算結(jié)果預(yù)估出運(yùn)行期間 JVM 的內(nèi)存運(yùn)轉(zhuǎn)模型。
部署多少臺(tái)機(jī)器,每臺(tái)機(jī)器配置如何。