每天 100w 次登陸請(qǐng)求,8G 內(nèi)存該如何設(shè)置 JVM 參數(shù)?
上周一個(gè)同學(xué)在阿里云技術(shù)面終面的時(shí)候被問(wèn)到這么一個(gè)問(wèn)題:假設(shè)一個(gè)每天100w次登陸請(qǐng)求的平臺(tái),一個(gè)服務(wù)節(jié)點(diǎn) 8G 內(nèi)存,該如何設(shè)置JVM參數(shù)?。
下面幫大家梳理一下,除了直接給出JVM參數(shù)配置方案,這個(gè)問(wèn)題的核心其實(shí)在于考察分析問(wèn)題的思路。
Step1:新系統(tǒng)上線如何規(guī)劃容量?
1.套路總結(jié)
任何新的業(yè)務(wù)系統(tǒng)在上線以前都需要去估算服務(wù)器配置和JVM的內(nèi)存參數(shù),這個(gè)容量與資源規(guī)劃并不僅僅是系統(tǒng)架構(gòu)師的隨意估算的,需要根據(jù)系統(tǒng)所在業(yè)務(wù)場(chǎng)景去估算,推斷出來(lái)一個(gè)系統(tǒng)運(yùn)行模型,評(píng)估JVM性能和GC頻率等等指標(biāo)。以下是我結(jié)合大牛經(jīng)驗(yàn)以及自身實(shí)踐來(lái)總結(jié)出來(lái)的一個(gè)建模步驟:
- 計(jì)算業(yè)務(wù)系統(tǒng)每秒鐘創(chuàng)建的對(duì)象會(huì)佔(zhàn)用多大的內(nèi)存空間,然后計(jì)算集群下的每個(gè)系統(tǒng)每秒的內(nèi)存佔(zhàn)用空間(對(duì)象創(chuàng)建速度)
- 設(shè)置一個(gè)機(jī)器配置,估算新生代的空間,比較不同新生代大小之下,多久觸發(fā)一次MinorGC。
- 為了避免頻繁GC,就可以重新估算需要多少機(jī)器配置,部署多少臺(tái)機(jī)器,給JVM多大內(nèi)存空間,新生代多大空間。
- 根據(jù)這套配置,基本可以推算出整個(gè)系統(tǒng)的運(yùn)行模型,每秒創(chuàng)建多少對(duì)象,1s以后成為垃圾,系統(tǒng)運(yùn)行多久新生代會(huì)觸發(fā)一次GC,頻率多高。
2.套路實(shí)戰(zhàn)——以登錄系統(tǒng)為例
有些同學(xué)看到這些步驟還是發(fā)憷,說(shuō)的好像是那么回事,一到實(shí)際項(xiàng)目中到底怎麼做我還是不知道!
光說(shuō)不練假把式,以登錄系統(tǒng)為例模擬一下推演過(guò)程:
- 假設(shè)每天100w次登陸請(qǐng)求,登陸峰值在早上,預(yù)估峰值時(shí)期每秒100次登陸請(qǐng)求。
- 假設(shè)部署3臺(tái)服務(wù)器,每臺(tái)機(jī)器每秒處理30次登陸請(qǐng)求,假設(shè)一個(gè)登陸請(qǐng)求需要處理1秒鐘,JVM新生代里每秒就要生成30個(gè)登陸對(duì)象,1s之后請(qǐng)求完畢這些對(duì)象成為了垃圾。
- 一個(gè)登陸請(qǐng)求對(duì)象假設(shè)20個(gè)字段,一個(gè)對(duì)象估算500字節(jié),30個(gè)登陸佔(zhàn)用大約15kb,考慮到RPC和DB操作,網(wǎng)絡(luò)通信、寫庫(kù)、寫緩存一頓操作下來(lái),可以擴(kuò)大到20-50倍,大約1s產(chǎn)生幾百k-1M數(shù)據(jù)。
- 假設(shè)2C4G機(jī)器部署,分配2G堆內(nèi)存,新生代則只有幾百M(fèi),按照1s1M的垃圾產(chǎn)生速度,幾百秒就會(huì)觸發(fā)一次MinorGC了。
- 假設(shè)4C8G機(jī)器部署,分配4G堆內(nèi)存,新生代分配2G,如此需要幾個(gè)小時(shí)才會(huì)觸發(fā)一次MinorGC。
所以,可以粗略的推斷出來(lái)一個(gè)每天100w次請(qǐng)求的登錄系統(tǒng),按照4C8G的3實(shí)例集群配置,分配4G堆內(nèi)存、2G新生代的JVM,可以保障系統(tǒng)的一個(gè)正常負(fù)載。
基本上把一個(gè)新系統(tǒng)的資源評(píng)估了出來(lái),所以搭建新系統(tǒng)要每個(gè)實(shí)例需要多少容量多少配置,集群配置多少個(gè)實(shí)例等等這些,并不是拍拍腦袋和胸脯就可以決定的下來(lái)的。
Step2:該如何進(jìn)行垃圾回收器的選擇?
吞吐量還是響應(yīng)時(shí)間
首先引入兩個(gè)概念:吞吐量和低延遲
吞吐量 = CPU在用戶應(yīng)用程序運(yùn)行的時(shí)間 / (CPU在用戶應(yīng)用程序運(yùn)行的時(shí)間 + CPU垃圾回收的時(shí)間)
響應(yīng)時(shí)間 = 平均每次的GC的耗時(shí)
通常,吞吐優(yōu)先還是響應(yīng)優(yōu)先這個(gè)在JVM中是一個(gè)兩難之選。
堆內(nèi)存增大,gc一次能處理的數(shù)量變大,吞吐量大;但是gc一次的時(shí)間會(huì)變長(zhǎng),導(dǎo)致后面排隊(duì)的線程等待時(shí)間變長(zhǎng);相反,如果堆內(nèi)存小,gc一次時(shí)間短,排隊(duì)等待的線程等待時(shí)間變短,延遲減少,但一次請(qǐng)求的數(shù)量變小(并不絕對(duì)符合)。
無(wú)法同時(shí)兼顧,是吞吐優(yōu)先還是響應(yīng)優(yōu)先,這是一個(gè)需要權(quán)衡的問(wèn)題。
垃圾回收器設(shè)計(jì)上的考量
- JVM在GC時(shí)不允許一邊垃圾回收,一邊還創(chuàng)建新對(duì)象(就像不能一邊打掃衛(wèi)生,還在一邊扔垃圾)。
- JVM需要一段Stop the world的暫停時(shí)間,而STW會(huì)造成系統(tǒng)短暫停頓不能處理任何請(qǐng)求;
- 新生代收集頻率高,性能優(yōu)先,常用復(fù)制算法;老年代頻次低,空間敏感,避免復(fù)制方式。
- 所有垃圾回收器的涉及目標(biāo)都是要讓GC頻率更少,時(shí)間更短,減少GC對(duì)系統(tǒng)影響!
CMS 和 G1
目前主流的垃圾回收器配置是新生代采用ParNew,老年代采用CMS組合的方式,或者是完全采用G1回收器,
從未來(lái)的趨勢(shì)來(lái)看,G1是官方維護(hù)和更為推崇的垃圾回收器。
圖片
業(yè)務(wù)系統(tǒng):
- 延遲敏感的推薦CMS。因?yàn)?CMS 在在最耗時(shí)的兩個(gè)階段(并發(fā)標(biāo)記和垃圾回收)都沒(méi)有發(fā)生 STW,而需要 STW 的階段都以很快速度完成
- 大內(nèi)存服務(wù),要求高吞吐的,采用 G1 回收器
Step3:如何對(duì)各個(gè)分區(qū)的比例、大小進(jìn)行規(guī)劃
一般的思路為:
首先,JVM最重要最核心的參數(shù)是去評(píng)估內(nèi)存和分配,第一步需要指定堆內(nèi)存的大小,這個(gè)是系統(tǒng)上線必須要做的,-Xms 初始堆大小,-Xmx 最大堆大小,后臺(tái)Java服務(wù)中一般都指定為系統(tǒng)內(nèi)存的一半,過(guò)大會(huì)佔(zhàn)用服務(wù)器的系統(tǒng)資源,過(guò)小則無(wú)法發(fā)揮JVM的最佳性能。
其次,需要指定-Xmn新生代的大小,這個(gè)參數(shù)非常關(guān)鍵,靈活度很大,雖然sun官方推薦為3/8大小,但是要根據(jù)業(yè)務(wù)場(chǎng)景來(lái)定,針對(duì)于無(wú)狀態(tài)或者輕狀態(tài)服務(wù)(現(xiàn)在最常見(jiàn)的業(yè)務(wù)系統(tǒng)如Web應(yīng)用)來(lái)說(shuō),一般新生代甚至可以給到堆內(nèi)存的3/4大?。魂P(guān)注公z號(hào):碼猿技術(shù)專欄,回復(fù)關(guān)鍵詞:1111 獲取阿里內(nèi)部java性能調(diào)優(yōu)手冊(cè)!而對(duì)于有狀態(tài)服務(wù)(常見(jiàn)如IM服務(wù)、網(wǎng)關(guān)接入層等系統(tǒng))新生代可以按照默認(rèn)比例1/3來(lái)設(shè)置。服務(wù)有狀態(tài),則意味著會(huì)有更多的本地緩存和會(huì)話狀態(tài)信息常駐內(nèi)存,應(yīng)為要給老年代設(shè)置更大的空間來(lái)存放這些對(duì)象。
最后,是設(shè)置-Xss棧內(nèi)存大小,設(shè)置單個(gè)線程棧大小,默認(rèn)值和JDK版本、系統(tǒng)有關(guān),一般默認(rèn)512~1024kb。一個(gè)后臺(tái)服務(wù)如果常駐線程有幾百個(gè),那麼棧內(nèi)存這邊也會(huì)佔(zhàn)用了幾百M(fèi)的大小。
圖片
對(duì)于8G內(nèi)存,一般分配一半的最大內(nèi)存就可以了,因?yàn)闄C(jī)器本上還要占用一定內(nèi)存,一般是分配4G內(nèi)存給JVM,
引入性能壓測(cè)環(huán)節(jié),測(cè)試同學(xué)對(duì)登錄接口壓至1s內(nèi)60M的對(duì)象生成速度,采用ParNew+CMS的組合回收器,
正常的JVM參數(shù)配置如下:
-Xms3072M
-Xmx3072M
-Xss1M
-XX:MetaspaceSize=256M
-XX:MaxMetaspaceSize=256M
-XX:SurvivorRatio=8
這樣設(shè)置可能會(huì)由于動(dòng)態(tài)對(duì)象年齡判斷原則導(dǎo)致頻繁full gc。為啥呢?
壓測(cè)過(guò)程中,短時(shí)間(比如20S后)Eden區(qū)就滿了,此時(shí)再運(yùn)行的時(shí)候?qū)ο笠呀?jīng)無(wú)法分配,會(huì)觸發(fā)MinorGC,
假設(shè)在這次GC后S1裝入100M,馬上過(guò)20S又會(huì)觸發(fā)一次MinorGC,多出來(lái)的100M存活對(duì)象+S1區(qū)的100M已經(jīng)無(wú)法順利放入到S2區(qū),此時(shí)就會(huì)觸發(fā)JVM的動(dòng)態(tài)年齡機(jī)制,將一批100M左右的對(duì)象推到老年代保存,持續(xù)運(yùn)行一段時(shí)間,系統(tǒng)可能一個(gè)小時(shí)候內(nèi)就會(huì)觸發(fā)一次FullGC。
按照默認(rèn)8:1:1的比例來(lái)分配時(shí), survivor區(qū)只有 1G的 10%左右,也就是幾十到100M,
如果 每次minor GC垃圾回收過(guò)后進(jìn)入survivor對(duì)象很多,并且survivor對(duì)象大小很快超過(guò) Survivor 的 50% , 那么會(huì)觸發(fā)動(dòng)態(tài)年齡判定規(guī)則,讓部分對(duì)象進(jìn)入老年代.
而一個(gè)GC過(guò)程中,可能部分WEB請(qǐng)求未處理完畢, 幾十兆對(duì)象,進(jìn)入survivor的概率,是非常大的,甚至是一定會(huì)發(fā)生的.
如何解決這個(gè)問(wèn)題呢?為了讓對(duì)象盡可能的在新生代的eden區(qū)和survivor區(qū), 盡可能的讓survivor區(qū)內(nèi)存多一點(diǎn),達(dá)到200兆左右,
于是我們可以更新下JVM參數(shù)設(shè)置:
-Xms3072M
-Xmx3072M
-Xmn2048M
-Xss1M
-XX:MetaspaceSize=256M
-XX:MaxMetaspaceSize=256M
-XX:SurvivorRatio=8
說(shuō)明:
‐Xmn2048M ‐XX:SurvivorRatio=8
年輕代大小2g,eden與survivor的比例為8:1:1,也就是1.6g:0.2g:0.2g
圖片
survivor達(dá)到200m,如果幾十兆對(duì)象到底survivor, survivor 也不一定超過(guò) 50%,這樣可以防止每次垃圾回收過(guò)后,survivor對(duì)象太早超過(guò) 50%,從而降低了因?yàn)閷?duì)象動(dòng)態(tài)年齡判斷原則導(dǎo)致的對(duì)象頻繁進(jìn)入老年代的問(wèn)題,
什么是 JVM 動(dòng)態(tài)年齡判斷規(guī)則?
對(duì)象進(jìn)入老年代的動(dòng)態(tài)年齡判斷規(guī)則(動(dòng)態(tài)晉升年齡計(jì)算閾值):Minor GC 時(shí),Survivor 中年齡 1 到 N 的對(duì)象大小超過(guò) Survivor 的 50% 時(shí),則將大于等于年齡 N 的對(duì)象放入老年代。
核心的優(yōu)化策略是:是讓短期存活的對(duì)象盡量都留在survivor里,不要進(jìn)入老年代,這樣在minor gc的時(shí)候這些對(duì)象都會(huì)被回收,不會(huì)進(jìn)到老年代從而導(dǎo)致full gc。
應(yīng)該如何去評(píng)估新生代內(nèi)存和分配合適?
這里特別說(shuō)一下,JVM最重要最核心的參數(shù)是去評(píng)估內(nèi)存和分配,
第一步需要指定堆內(nèi)存的大小,這個(gè)是系統(tǒng)上線必須要做的,-Xms 初始堆大小,-Xmx 最大堆大小,
后臺(tái)Java服務(wù)中一般都指定為系統(tǒng)內(nèi)存的一半,過(guò)大會(huì)佔(zhàn)用服務(wù)器的系統(tǒng)資源,過(guò)小則無(wú)法發(fā)揮JVM的最佳性能。
其次需要指定-Xmn新生代的大小,這個(gè)參數(shù)非常關(guān)鍵,靈活度很大,雖然sun官方推薦為3/8大小,但是要根據(jù)業(yè)務(wù)場(chǎng)景來(lái)定:
針對(duì)于無(wú)狀態(tài)或者輕狀態(tài)服務(wù)(現(xiàn)在最常見(jiàn)的業(yè)務(wù)系統(tǒng)如Web應(yīng)用)來(lái)說(shuō),一般新生代甚至可以給到堆內(nèi)存的3/4大??;
而對(duì)于有狀態(tài)服務(wù)(常見(jiàn)如IM服務(wù)、網(wǎng)關(guān)接入層等系統(tǒng))新生代可以按照默認(rèn)比例1/3來(lái)設(shè)置。
服務(wù)有狀態(tài),則意味著會(huì)有更多的本地緩存和會(huì)話狀態(tài)信息常駐內(nèi)存,應(yīng)為要給老年代設(shè)置更大的空間來(lái)存放這些對(duì)象。
step4:棧內(nèi)存大小多少比較合適?
-Xss棧內(nèi)存大小,設(shè)置單個(gè)線程棧大小,默認(rèn)值和JDK版本、系統(tǒng)有關(guān),一般默認(rèn)512~1024kb。一個(gè)后臺(tái)服務(wù)如果常駐線程有幾百個(gè),那麼棧內(nèi)存這邊也會(huì)佔(zhàn)用了幾百M(fèi)的大小。
step5:對(duì)象年齡應(yīng)該為多少才移動(dòng)到老年代比較合適?
假設(shè)一次minor gc要間隔二三十秒,并且,大多數(shù)對(duì)象一般在幾秒內(nèi)就會(huì)變?yōu)槔?/p>
如果對(duì)象這么長(zhǎng)時(shí)間都沒(méi)被回收,比如2分鐘沒(méi)有回收,可以認(rèn)為這些對(duì)象是會(huì)存活的比較長(zhǎng)的對(duì)象,從而移動(dòng)到老年代,而不是繼續(xù)一直占用survivor區(qū)空間。
所以,可以將默認(rèn)的15歲改小一點(diǎn),比如改為5,
那么意味著對(duì)象要經(jīng)過(guò)5次minor gc才會(huì)進(jìn)入老年代,整個(gè)時(shí)間也有一兩分鐘了(5*30s= 150s),和幾秒的時(shí)間相比,對(duì)象已經(jīng)存活了足夠長(zhǎng)時(shí)間了。
所以:可以適當(dāng)調(diào)整JVM參數(shù)如下:
‐Xms3072M
‐Xmx3072M
‐Xmn2048M
‐Xss1M
‐XX:MetaspaceSize=256M
‐XX:MaxMetaspaceSize=256M
‐XX:SurvivorRatio=8
‐XX:MaxTenuringThreshold=5
step6:多大的對(duì)象,可以直接到老年代比較合適?
對(duì)于多大的對(duì)象直接進(jìn)入老年代(參數(shù)-XX:PretenureSizeThreshold),一般可以結(jié)合自己系統(tǒng)看下有沒(méi)有什么大對(duì)象 生成,預(yù)估下大對(duì)象的大小,一般來(lái)說(shuō)設(shè)置為1M就差不多了,很少有超過(guò)1M的大對(duì)象,
所以:可以適當(dāng)調(diào)整JVM參數(shù)如下:
‐Xms3072M
‐Xmx3072M
‐Xmn2048M
‐Xss1M
‐XX:MetaspaceSize=256M
‐XX:MaxMetaspaceSize=256M
‐XX:SurvivorRatio=8
‐XX:MaxTenuringThreshold=5
‐XX:PretenureSizeThreshold=1M
step7:垃圾回收器CMS老年代的參數(shù)優(yōu)化
JDK8默認(rèn)的垃圾回收器是-XX:+UseParallelGC(年輕代)和-XX:+UseParallelOldGC(老年代),
如果內(nèi)存較大(超過(guò)4個(gè)G,只是經(jīng)驗(yàn) 值),還是建議使用G1.
這里是4G以內(nèi),又是主打“低延時(shí)” 的業(yè)務(wù)系統(tǒng),可以使用下面的組合:
ParNew+CMS(-XX:+UseParNewGC -XX:+UseConcMarkSweepGC)
新生代的采用ParNew回收器,工作流程就是經(jīng)典復(fù)制算法,在三塊區(qū)中進(jìn)行流轉(zhuǎn)回收,只不過(guò)采用多線程并行的方式加快了MinorGC速度。
老生代的采用CMS。再去優(yōu)化老年代參數(shù):比如老年代默認(rèn)在標(biāo)記清除以后會(huì)做整理,還可以在CMS的增加GC頻次還是增加GC時(shí)長(zhǎng)上做些取舍,
如下是響應(yīng)優(yōu)先的參數(shù)調(diào)優(yōu):
XX:CMSInitiatingOccupancyFractinotallow=70
設(shè)定CMS在對(duì)內(nèi)存占用率達(dá)到70%的時(shí)候開(kāi)始GC(因?yàn)镃MS會(huì)有浮動(dòng)垃圾,所以一般都較早啟動(dòng)GC)
XX:+UseCMSInitiatinpOccupancyOnly
和上面搭配使用,否則只生效一次
-XX:+AlwaysPreTouch
強(qiáng)制操作系統(tǒng)把內(nèi)存真正分配給IVM,而不是用時(shí)才分配。
綜上,只要年輕代參數(shù)設(shè)置合理,老年代CMS的參數(shù)設(shè)置基本都可以用默認(rèn)值,如下所示:
‐Xms3072M
‐Xmx3072M
‐Xmn2048M
‐Xss1M
‐XX:MetaspaceSize=256M
‐XX:MaxMetaspaceSize=256M
‐XX:SurvivorRatio=8
‐XX:MaxTenuringThreshold=5
‐XX:PretenureSizeThreshold=1M
‐XX:+UseParNewGC
‐XX:+UseConcMarkSweepGC
‐XX:CMSInitiatingOccupancyFractinotallow=70
‐XX:+UseCMSInitiatingOccupancyOnly
‐XX:+AlwaysPreTouch
參數(shù)解釋:
1.‐Xms3072M ‐Xmx3072M最小最大堆設(shè)置為3g,最大最小設(shè)置為一致防止內(nèi)存抖動(dòng)
2.‐Xss1M線程棧1m
3.‐Xmn2048M ‐XX:SurvivorRatio=8年輕代大小2g,eden與survivor的比例為8:1:1,也就是1.6g:0.2g:0.2g
4.-XX:MaxTenuringThreshold=5年齡為5進(jìn)入老年代 5.‐XX:PretenureSizeThreshold=1M大于1m的大對(duì)象直接在老年代生成
6.‐XX:+UseParNewGC ‐XX:+UseConcMarkSweepGC使用ParNew+cms垃圾回收器組合
7.‐XX:CMSInitiatingOccupancyFractinotallow=70老年代中對(duì)象達(dá)到這個(gè)比例后觸發(fā)fullgc
8.‐XX:+UseCMSInitiatinpOccupancyOnly 老年代中對(duì)象達(dá)到這個(gè)比例后觸發(fā)fullgc,每次
9.‐XX:+AlwaysPreTouch強(qiáng)制操作系統(tǒng)把內(nèi)存真正分配給IVM,而不是用時(shí)才分配。
step8:配置OOM時(shí)候的內(nèi)存dump文件和GC日志
額外增加了GC日志打印、OOM自動(dòng)dump等配置內(nèi)容,幫助進(jìn)行問(wèn)題排查
-XX:+HeapDumpOnOutOfMemoryError
在Out Of Memory,JVM快死掉的時(shí)候,輸出Heap Dump到指定文件。
不然開(kāi)發(fā)很多時(shí)候還真不知道怎么重現(xiàn)錯(cuò)誤。
路徑只指向目錄,JVM會(huì)保持文件名的唯一性,叫java_pid${pid}.hprof。
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=${LOGDIR}/
因?yàn)槿绻赶蛱囟ǖ奈募?,而文件已存在,反而不能寫入?/p>
輸出4G的HeapDump,會(huì)導(dǎo)致IO性能問(wèn)題,在普通硬盤上,會(huì)造成20秒以上的硬盤IO跑滿,
需要注意一下,但在容器環(huán)境下,這個(gè)也會(huì)影響同一宿主機(jī)上的其他容器。
GC的日志的輸出也很重要:
-Xloggc:/dev/xxx/gc.log
-XX:+PrintGCDateStamps
-XX:+PrintGCDetails
GC的日志實(shí)際上對(duì)系統(tǒng)性能影響不大,打日志對(duì)排查GC問(wèn)題很重要。
一份通用的 JVM 參數(shù)模板
一般來(lái)說(shuō),大企業(yè)或者架構(gòu)師團(tuán)隊(duì),都會(huì)為項(xiàng)目的業(yè)務(wù)系統(tǒng)定制一份較為通用的JVM參數(shù)模板,但是許多小企業(yè)和團(tuán)隊(duì)可能就疏于這一塊的設(shè)計(jì),如果老板某一天突然讓你負(fù)責(zé)定制一個(gè)新系統(tǒng)的JVM參數(shù),你上網(wǎng)去搜大量的JVM調(diào)優(yōu)文章或博客,結(jié)果發(fā)現(xiàn)都是零零散散的、不成體系的JVM參數(shù)講解,根本下不了手,這個(gè)時(shí)候你就需要一份較為通用的JVM參數(shù)模板了,不能保證性能最佳,但是至少能讓JVM這一層是穩(wěn)定可控的,
在這里給大家總結(jié)了一份模板:
基于 4C8G 系統(tǒng)的 ParNew+CMS 回收器模板(響應(yīng)優(yōu)先),新生代大小根據(jù)業(yè)務(wù)靈活調(diào)整!
-Xms4g
-Xmx4g
-Xmn2g
-Xss1m
-XX:SurvivorRatio=8
-XX:MaxTenuringThreshold=10
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFractinotallow=70
-XX:+UseCMSInitiatingOccupancyOnly
-XX:+AlwaysPreTouch
-XX:+HeapDumpOnOutOfMemoryError
-verbose:gc
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
-Xloggc:gc.log
如果是GC的吞吐優(yōu)先,推薦使用G1,基于8C16G系統(tǒng)的G1回收器模板:
G1收集器自身已經(jīng)有一套預(yù)測(cè)和調(diào)整機(jī)制了,因此我們首先的選擇是相信它,即調(diào)整-XX:MaxGCPauseMillis=N參數(shù),這也符合G1的目的——讓GC調(diào)優(yōu)盡量簡(jiǎn)單!
同時(shí)也不要自己顯式設(shè)置新生代的大小(用-Xmn或-XX:NewRatio參數(shù)),如果人為干預(yù)新生代的大小,會(huì)導(dǎo)致目標(biāo)時(shí)間這個(gè)參數(shù)失效。
-Xms8g
-Xmx8g
-Xss1m
-XX:+UseG1GC
-XX:MaxGCPauseMillis=150
-XX:InitiatingHeapOccupancyPercent=40
-XX:+HeapDumpOnOutOfMemoryError
-verbose:gc
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
-Xloggc:gc.log
G1參數(shù) | 描述 | 默認(rèn)值 |
XX:MaxGCPauseMillis=N | 最大GC停頓時(shí)間。柔性目標(biāo),JVM滿足90%,不保證100%。 | 200 |
-XX:nitiatingHeapOccupancyPercent=n | 當(dāng)整個(gè)堆的空間使用百分比超過(guò)這個(gè)值時(shí),就會(huì)融發(fā)MixGC | 45 |
針對(duì)-XX:MaxGCPauseMillis來(lái)說(shuō),參數(shù)的設(shè)置帶有明顯的傾向性:調(diào)低↓:延遲更低,但MinorGC頻繁,MixGC回收老年代區(qū)減少,增大Full GC的風(fēng)險(xiǎn)。調(diào)高↑:?jiǎn)未位厥崭嗟膶?duì)象,但系統(tǒng)整體響應(yīng)時(shí)間也會(huì)被拉長(zhǎng)。
針對(duì)InitiatingHeapOccupancyPercent來(lái)說(shuō),調(diào)參大小的效果也不一樣:調(diào)低↓:更早觸發(fā)MixGC,浪費(fèi)cpu。調(diào)高↑:堆積過(guò)多代回收region,增大FullGC的風(fēng)險(xiǎn)。
調(diào)優(yōu)總結(jié)
系統(tǒng)在上線前的綜合調(diào)優(yōu)思路:
1、業(yè)務(wù)預(yù)估:根據(jù)預(yù)期的并發(fā)量、平均每個(gè)任務(wù)的內(nèi)存需求大小,然后評(píng)估需要幾臺(tái)機(jī)器來(lái)承載,每臺(tái)機(jī)器需要什么樣的配置。
2、容量預(yù)估:根據(jù)系統(tǒng)的任務(wù)處理速度,然后合理分配Eden、Surivior區(qū)大小,老年代的內(nèi)存大小。
3、回收器選型:響應(yīng)優(yōu)先的系統(tǒng),建議采用ParNew+CMS回收器;吞吐優(yōu)先、多核大內(nèi)存(heap size≥8G)服務(wù),建議采用G1回收器。
4、優(yōu)化思路:讓短命對(duì)象在MinorGC階段就被回收(同時(shí)回收后的存活對(duì)象<Survivor區(qū)域50%,可控制保留在新生代),長(zhǎng)命對(duì)象盡早進(jìn)入老年代,不要在新生代來(lái)回復(fù)制;盡量減少Full GC的頻率,避免FGC系統(tǒng)的影響。
5、到目前為止,總結(jié)到的調(diào)優(yōu)的過(guò)程主要基于上線前的測(cè)試驗(yàn)證階段,所以我們盡量在上線之前,就將機(jī)器的JVM參數(shù)設(shè)置到最優(yōu)!
JVM調(diào)優(yōu)只是一個(gè)手段,但并不一定所有問(wèn)題都可以通過(guò)JVM進(jìn)行調(diào)優(yōu)解決,大多數(shù)的Java應(yīng)用不需要進(jìn)行JVM優(yōu)化,我們可以遵循以下的一些原則:
- 上線之前,應(yīng)先考慮將機(jī)器的JVM參數(shù)設(shè)置到最優(yōu);
- 減少創(chuàng)建對(duì)象的數(shù)量(代碼層面);
- 減少使用全局變量和大對(duì)象(代碼層面);
- 優(yōu)先架構(gòu)調(diào)優(yōu)和代碼調(diào)優(yōu),JVM優(yōu)化是不得已的手段(代碼、架構(gòu)層面);
- 分析GC情況優(yōu)化代碼比優(yōu)化JVM參數(shù)更好(代碼層面);
通過(guò)以上原則,我們發(fā)現(xiàn),其實(shí)最有效的優(yōu)化手段是架構(gòu)和代碼層面的優(yōu)化,而JVM優(yōu)化則是最后不得已的手段,也可以說(shuō)是對(duì)服務(wù)器配置的最后一次“壓榨”。