配音秀做素材網(wǎng)站惠州seo推廣公司
背景
????????最近負責(zé)了一個審批流程新項目,帶領(lǐng)了幾個小伙伴,哼哧哼哧的干了3個月左右,終于在三月底完美上線了,好消息是線上客戶用的很絲滑,除了幾個非常規(guī)的業(yè)務(wù)提單之外,幾乎沒有什么大的問題,但是美中不足的是,發(fā)現(xiàn)每個pod的GC頻率非常高,基本上30分鐘就會有一次FGC,導(dǎo)致每次流量高峰的時候,會有一部分客戶反饋,系統(tǒng)有些卡頓,觀察監(jiān)控平臺發(fā)現(xiàn),每天流量高峰的時候FGC竟然達到了驚人的5分鐘每次,每次GC的時間差不多有400-700ms,此時,部分接口的耗時達到了5s,因此接口優(yōu)化和參數(shù)調(diào)優(yōu)迫在眉睫;
????????因為本項目是基礎(chǔ)服務(wù),每個業(yè)務(wù)方都會調(diào)用,所以當時申請節(jié)點內(nèi)存大小的時候就富裕了一點,共部署了4個pod,每個pod資源是8核16G,但是觀察監(jiān)控平臺發(fā)現(xiàn),每個pod內(nèi)存只使用不到2G,其中eden 200M old 500m survivor更是只有可憐的96m左右,導(dǎo)致年輕代很容易就占滿了,存活的對象就被轉(zhuǎn)移到老年代了,由于老年代分配的內(nèi)存也特別少,QPS一高就會頻繁的觸發(fā)FullGC,導(dǎo)致系統(tǒng)卡頓甚至接口超時。
????????排查代碼發(fā)現(xiàn)有一個占比60%量一個接口雖然查詢的表比較單一,但是查詢了所有字段,其中一個字段存儲的是一個JSON,但業(yè)務(wù)中卻又沒有使用到。
JVM常見參數(shù)
一、配置垃圾收集器
1、Serial垃圾收集器(新生代)
?????????開啟:-XX:+UseSerialGC
?????????關(guān)閉:-XX:-UseSerialGC
?????????//新生代使用Serial ?老年代則使用SerialOld
??
?2、ParNew垃圾收集器(新生代)
?????????開啟 -XX:+UseParNewGC
?????????關(guān)閉 -XX:-UseParNewGC
?????????//新生代使用功能ParNew 老年代則使用功能CMS
??
?3、Parallel Scavenge收集器(新生代)
?????????開啟 -XX:+UseParallelOldGC
?????????關(guān)閉 -XX:-UseParallelOldGC
?????????//新生代使用功能Parallel Scavenge 老年代將會使用Parallel Old收集器
??
?4、ParallelOld垃圾收集器(老年代)
????????開啟 -XX:+UseParallelGC
????????關(guān)閉 -XX:-UseParallelGC
????????//新生代使用功能Parallel Scavenge 老年代將會使用Parallel Old收集器
??
?5、CMS垃圾收集器(老年代)
????????開啟 -XX:+UseConcMarkSweepGC
????????關(guān)閉 -XX:-UseConcMarkSweepGC
??
?6、G1垃圾收集器
?????????開啟 -XX:+UseG1GC
?????????關(guān)閉 -XX:-UseG1GC
二、堆內(nèi)存相關(guān)配置
設(shè)置堆初始值
?????????指令1:-Xms2g
??
?設(shè)置堆區(qū)最大值
?????????指令1:-Xmx2g
??
?新生代內(nèi)存配置
?????????指令1:-Xmn512m
??
?2個survivor區(qū)和Eden區(qū)大小比率
?????????指令:-XX:SurvivorRatio=6 ?//S區(qū)和Eden區(qū)占新生代比率為1:6,兩個S區(qū)2:6(默認是8,即8:1:1)
??
?新生代和老年代的占比
?-XX:NewRatio=4 ?//表示新生代:老年代 = 1:4 即老年代占整個堆的4/5;默認值=2
三、GC并行執(zhí)行線程數(shù)
????????-XX:ParallelGCThreads=16
四、進入老年代的GC年齡
?????????-XX:InitialTenuringThreshol=7 //年輕代對象轉(zhuǎn)換為老年代對象最小年齡值,默認值7,對象在堅持過一次Minor GC之后,年齡就加1,每個對象在堅持過一次Minor GC之后,年齡就增加1
??
?????????-XX:MaxTenuringThreshold=15 //年輕代對象轉(zhuǎn)換為老年代對象最大年齡值,默認值15
五、GC日志信息配置
????????-Xloggc:/data/gclog/gc.log//固定路徑名稱生成
?????????-Xloggc:/home/GCEASY/gc-%t.log //根據(jù)時間生成
????????打印GC的詳細日志?
????????開啟 -XX:+PrintGCDetails
????????關(guān)閉 -XX:-PrintGCDetails
六、在Full GC時生成dump文件
????????-XX:+HeapDumpBeforeFullGC ? ? ? //實現(xiàn)在Full GC前dump
????????-XX:+HeapDumpAfterFullGC ? ? ? ?//實現(xiàn)在Full GC后dump。
????????-XX:HeapDumpPath=e:\dump ? ? ? ?//設(shè)置Dump保存的路徑
調(diào)優(yōu)過程
? ? 一、業(yè)務(wù)調(diào)優(yōu)
? ? ? ?業(yè)務(wù)調(diào)優(yōu)就不展開講述了,主要是用到了arthas這個調(diào)優(yōu)工具,trace了耗時比較久的接口,排除不優(yōu)雅的編碼之后,就來到了數(shù)據(jù)層面,由于項目使用的是postgres sql,并且已經(jīng)分庫+分區(qū)了,核心表的數(shù)據(jù)量級也是百萬級別,所以最終關(guān)注的是索引,使用explain查看核心sql的執(zhí)行計劃,看其是否命中索引;
????????補充一點,由于項目有一張歷史表的數(shù)據(jù)量比較大,8000萬左右,并且業(yè)務(wù)中也需要使用到,每個單據(jù)號對應(yīng)的審批流程一般是流程節(jié)點的10倍左右,比如某個審批流程10個節(jié)點,那么改流程結(jié)束后就會產(chǎn)生100+條數(shù)據(jù),在列表中使用到了改表中的某些數(shù)據(jù),起初直接根據(jù)單據(jù)號進行查詢,那么分頁條數(shù)為1000的時候每次就會查詢1000*100條記錄,而業(yè)務(wù)真正需要關(guān)注的只是10個節(jié)點的審批結(jié)果而已,白白浪費90%的查詢記錄,因此,在業(yè)務(wù)中冗余了一個節(jié)點字段,標識是否是節(jié)點的審批結(jié)果,每次查詢除了使用單據(jù)號之外,加上該字段,就大大的過濾了記錄數(shù)量,這樣做法有如下好處:
? ? ? ? ①減少網(wǎng)絡(luò)傳輸量
? ? ? ? ②降低內(nèi)存使用(起初是內(nèi)存過濾,高cpu操作)
? ? ? ? ③防止OOM
二、JVM參數(shù)調(diào)優(yōu)
? ? ? ? 第一版
? ? ? ? ? ? ? ? 第一版比較偷懶,直接上了G1,因此G1不用配置過多的參數(shù),很多就自適應(yīng)調(diào)節(jié),但是一上線發(fā)現(xiàn)cpu就很容易飆升到80以上,并且接口耗時也慢了一倍,起初每個接口的耗時到該200ms,G1之后就變成400ms,終于在運行3天之后,就有監(jiān)控告警有大量的接口請求超時,觀察到j(luò)vm堆內(nèi)存使用長時間100%,導(dǎo)致健康檢查機制強行將節(jié)點重啟了(5s檢查一次,15未檢查到就重啟)為了不影響業(yè)務(wù)先增加了兩個節(jié)點,降低QPS,從而降低堆內(nèi)存使用。
? ? ? ? ? ? ? ? 具體的參數(shù)配置
-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:+PrintGC -Xloggc:/data/logs/gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/logs/ -XX:+UseG1GC -Xmx9G -Xms9G -XX:MaxDirectMemorySize=1g -XX:ConcGCThreads=8 -XX:MaxGCPauseMillis=500
? ? ? ? 第二版
????????由于公司所有的項目都是K8s集群部署,所以JVM參數(shù)基本都用的是默認參數(shù),這一次只是設(shè)置了Xms=11g,Xmx=10g,Xmn=4g,但是發(fā)現(xiàn)年輕代的from和to的比例不對,按照正常的默認8:1:1,應(yīng)該是410m左右,但是監(jiān)控上面顯示的始終是121m,這樣的話QPS達到高峰的時候,老年代上升的速率就比較快,3.76G,基本上12h就用完觸發(fā)了FGC,這顯然是不合理的,因為業(yè)務(wù)邏輯中沒有需要常駐內(nèi)存的對象,基本上朝生夕滅的,在年輕代就應(yīng)該被回收,而導(dǎo)致出現(xiàn)這個原因是from和to的內(nèi)存太小了,存貨了15次之后的對象就被轉(zhuǎn)移到老年代了。
? ? ? ? 開始的時候使用-XX:-UseAdaptiveSizePolicy?–XX:SurvivorRatio=8,確實改變年輕代的具體分配內(nèi)存,但是使用 jmsp -heap 1命令發(fā)現(xiàn),jvm的垃圾回收器是ParallelGC,并不是我們想要的CMS,因此不論年輕代from和to的設(shè)置了多大空間,其使用始終不會超過121m,老年代還是晉升的速率比較快,并沒有徹底解決問題。具體參數(shù)配置如下
-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -Xmx11g -Xms11g -Xmn5g -XX:PermSize=1g -XX:MaxPermSize=1g -XX:SurvivorRatio=8 -XX:-UseAdaptiveSizePolicy -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps
? ? ? ? 第三版 最終版本
? ? ? 開啟CMS收集參數(shù)? -XX:+UseConcMarkSweepGC,監(jiān)控和運行變得正常,堆空間各個區(qū)域的分配也是正常的。
-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:+UseConcMarkSweepGC -Xmx11g -Xms11g -Xmn5g -XX:PermSize=1g -XX:MaxPermSize=1g -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -Xloggc:/data/logs/gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/logs
????????小結(jié)
? ? ? ? 容器部署節(jié)點只分配1G,具體原因可以參考這篇文章頻繁 GC 問題排查以及UseContainerSupport與MaxRAMPercentage的正確使用-CSDN博客
????????按照理論上第二次調(diào)優(yōu)就已經(jīng)能夠滿足業(yè)務(wù)需求了,但是依據(jù)《深入理解Java虛擬機》講的,jdk8默認的垃圾收集器是CMS,那么年輕代的eden、from和to分配內(nèi)存空間的比例應(yīng)該是8:1:1,顯然目前我的數(shù)據(jù)不正確,進入到pod節(jié)點發(fā)現(xiàn)jvm使用的是ParallelGC,如下圖所示
jvm調(diào)優(yōu)常見問題
? ? ? ? 常用命令
? ? ? ? jmap -heap pid 查看內(nèi)存使用情況
推薦配置
????????8C16G下的參數(shù)配置
綜上所述,8C16G下,推薦使用如下的參數(shù)設(shè)置:
-Xmx12g -Xms12g
-XX:ParallelGCThreads=8
-XX:ConcGCThreads=2
-XX:+UseConcMarkSweepGC
-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=70
-XX:MaxGCPauseMillis=100 ?// 按業(yè)務(wù)情況來定
-XX:+PrintGCTimeStamps
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps