現象
young gc時間達到了150-500ms之間,每個服務器的時間不一樣,都在這個區間,監控的tp99和tp999有明顯的毛刺
環境
docker環境,服務器規格:2c4g,容器:tomcat8,jdk:8,回收器:cms
排查過程
1、首先想到查看gc日誌,發現有如下日誌:
[GC (Allocation Failure) [ParNew: 561827K->2873K(629120K), 0.4622482 secs] 1149586K->590676K(2027264K), 0.4626134 secs] [Times: user=0.92 sys=0.00, real=0.46 secs]
可以看到長達400多ms
2、然後想,難道是新生代內存太多了,通過jmap head命令把當前堆的信息輸出到文件,查看:
可以看到新生代並不大,根據前面的GC日誌,也就回收了500多M的對象,不可能這麼慢
3、然後想,難道docker負載比較大,查看docker服務器信息,cpu、內存、網絡、磁盤都正常
4、既然docker本身很正常,哪就奇怪了,是什麼導致young gc比較慢呢?做什麼事,耗費了時間呢?但又感覺無從下手
5、後與架構師聊了下這個情況,提到gc的線程數,我說是默認的,默認的情況下,gc會開啓服務器的cpu的核數的個數的線程數,這裏要最關鍵的是取的是物理機的核數,而不是docker的核數,我查了下這個docker宿主機的核數是60,意味着什麼,我想大家都知道了,開啓了太多的線程,開啓線程也需要時間,線程之間的競爭也多起來,時間自然消耗就大了
解決
指定gc線程數和編譯線程數
-XX:ParallelGCThreads=2 -XX:CICompilerCount=2
觀察一段時間,發現young gc時間下降到了20-60ms之間的水平,提高了6,7倍吧,再看監控的tp99和tp999瞬間絲滑了很多
額外收穫
發現之前,容器重啓cpu彪高的問題也沒消失了,原來是它的鍋
總結
容器最佳配置
1、堆內存配置
1C2G容器:-Xms512m -Xmx512m,負載較高的可設置爲-Xms800m -Xmx800m
2C4G容器:-Xms2048m -Xmx2048m
4C8G容器:-Xms4096m -Xmx4096m
2、GC及編譯線程配置
-XX:ParallelGCThreads=[cpu核數]
注意:如果ParallelGCThreads 沒有配置,默認會使用物理機的cpu核數設置回收線程數,這會嚴重降低GC性能且產生過多GC線程,因此所有容器都應配置該參數;
-XX:CICompilerCount=[cpu核數 >2 ? cpu核數 : 2]
可以優化類文件編譯性能,默認會使用物理機cpu核數進行設置,所有容器都應該手動配置;
3、元空間
-XX:MetaspaceSize 元空間;
-XX:MaxMetaspaceSize 最大元空間;
元空間對應以前的永久代內存,推薦設置爲256~512M;
注意:MaxPermSize參數在JDK8中已經廢棄,應使用MetaspaceSize 進行設置4、回收器類型
-XX:+UseParallelGC 並行回收器(默認)
-XX:+UseConcMarkSweepGC CMS回收器
-XX:+UseG1GC G1回收器
Jdk1.8的容器推薦使用G1,舊版本jdk容器推薦使用CMS,這兩個回收器都是響應時間優先的算法,對響應時間敏感的應用推薦使用;
GC日誌
當GC有問題時,可以打印GC日誌幫助排查,相關參數有:
-Xloggc:/export/Logs/jvm/gc.log
-XX:GCLogFileSize=100M
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+ PrintGCCause
-XX:+PrintClassHistogramBeforeFullGC
-XX:+PrintClassHistogramAfterFullGC
-XX:+HeapDumpBeforeFullGC
-XX:+HeapDumpAfterFullGC