JVM 調優案例分析
一、腳本引擎導緻metaspace fullgc及記憶體溢出
現象:系統日常運作正常突然發生fullgc甚至記憶體溢出,重新開機後恢複正常但是過了幾天又會突然發生頻繁fullgc觸發告警,告警資訊如下:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5COxQDN4EmM2M2NwYTN5IjMzYzX4QDMwETM1EzLclDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
首先發生fullgc,我們需要定位fullgc發生在jvm哪個區域?我們可以通過cat也可以通過gc日志查詢發生的區域,目前有兩種方式可以快速定位到gc區域:
1、Cat的Problem中可以直接查詢error的詳細資訊(時間比較久了找不到對應的cat截圖了)
2、通過gclog查詢(大部分系統沒有添加gc日志列印),常見配置如下
# 必備
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintTenuringDistribution
-XX:+PrintHeapAtGC
-XX:+PrintReferenceGC
-XX:+PrintGCApplicationStoppedTime
# 可選
-XX:+PrintSafepointStatistics
-XX:PrintSafepointStatisticsCount=1
# GC日志輸出的檔案路徑
-Xloggc:/path/to/gc-%t.log
# 開啟日志檔案分割
-XX:+UseGCLogFileRotation
# 最多分割幾個檔案,超過之後從頭檔案開始寫
-XX:NumberOfGCLogFiles=14
# 每個檔案上限大小,超過就觸發分割
-XX:GCLogFileSize=100M
通過以上分析可以看出fullgc發生再metaspace區,按照以往的經驗,Metaspace在系統穩定運作一段時間後占用空間應該比較穩定才對。我們知道Metaspace主要存儲類的中繼資料,比如我們加載了一個類,出現頻繁fullgc的時候說明在不斷的加載類,194621k->165280k發現也解除安裝了不少類,說明在頻繁地生成大量”一次性“的類,于是分析dump檔案:
具體步驟:
1.申請目前機器堡壘機權限
2.登入堡壘機
3.檢視目前Java程序
ps -ef|grep java
4.将此機器下線(防止進行dump檔案時對其他業務造成影響)
5.進行應用dump操作
sudo -u tuhu jmap -dump:format=b,file=/tmp/heapDump.hprof [pid]
6.修改dump檔案權限
sudo chmod 644 /tmp/heapDump.hprof
7.對dump檔案進行壓縮操作(dump的檔案很大 一般不壓縮的話 很難下載下傳的)
cd /tmp 進入到 /tmp 目錄
tar -zcvf dump.tar.gz ./heapDump.hprof
進行壓縮,得到壓縮檔案 dump.tar.gz
8.在檔案視圖裡面 下載下傳dump檔案
9.下載下傳到本地解壓:
//解壓tar.gz
tar -xzvf dump.tar.gz
通過visualvm分析(mat沒有發現問題)發現頻繁建立了很多Script類,看reference是Lhs類,于是分析代碼Lhs的compile方法:
/**
* 使用aviator編譯原始表達式
*/
public void compile() {
RuleAssert.requireNotEmptyWhenCompiling(expression,
RuleErrorEnum.RULE_COMPILE_INVALID_EXPRESSION);
compiledExpression = AviatorEvaluator.compile(this.expression);
}
經過查詢aviator官網文檔分析發現确實存在記憶體溢出的問題,在編譯表達式的時候每次都産生新的匿名類,這些類會占用 JVM 方法區(Perm 或者 metaspace)
解決方案:問題修複比較簡單編譯的時候使用緩存模式,如:AviatorEvaluator.compile(this.expression, true),重新釋出後metaspace穩定
二、kafka消費不均衡導緻cpu負載高
現象:廣告投放服務突然出現很多cpu負載過高告警,尤其是在釋出的時候告警頻繁,告警資訊如下:
出現負載過高首先我們要明白什麼場景下回導緻cpu負載過高?才能快速定位到問題原因,個人總結cpu占用高主要有以下幾點:
1、存線上程無限循環的占用cpu
2、Young GC頻繁導緻CPU占用率飙升
3、建立了大量線程的應用程式導緻cpu飙升
4、存在密集型計算線程
這裡推薦一個比較好用的工具 arthas(阿爾薩斯),提供了很多指令如:thread,jvm,memory等,于是我們通過thread分析線程情況:
通過線程資訊可以快速定位到是mkt_ta_track_topic.sub消費線程占用了大量的cpu資源,通過簡單的分析定位到問題,主要因為兩個問題:
1、ta埋點(消息量很大)測試目前是做了簡單的判斷列印日志,沒有做其他業務邏輯并且使用了多線程消費,消費速度很快相當于空循環
2、mkt_ta_track_topic當時是申請了8個分區,目前隻有5台機器意味着有部分機器可能消費多個分區,此時占用cpu更嚴重;另外服務釋出的時候也會導緻kafka分區重配置設定也會出現上面說的負載過高問題
解決方案:
1、調整線程池多線程消費為單線程消費(後面做業務正常處理再做對應的調整)
2、申請擴充機器添加3台服務,為什麼添加機器不是調整分區?服務負載本來就很高,借此機會擴容,擴容之後資源使用率依然有75.97%
三:FullGC 停頓時間過長導緻上遊調用服務逾時
背景:新接手了一個服務,上遊回報偶爾調用逾時,和上遊确認逾時發生時間點和具體逾時時間,(逾時時間為 1000 ms)。
檢視當時系統運作情況,發現正在發生 FullGC ,且停頓時間超過 1000 ms。
要對 FullGC 停頓時間太長調優,我們就得先知道影響 FullGc 停頓時間太長的原因有哪些,通常來說,有以下幾種:
- 堆記憶體過大
- 使用了非低延遲的垃圾收集器(GC)
- FGC 期間系統在做其他操作,如 swap 交換空間
- 堆記憶體碎片化嚴重
- 顯示System.gc調用
經排查發現屬于第二種情況,JDK 使用為 jdk8 ,預設垃圾收集器為 Parallel Scanenge 收集器,該收集器主打吞吐量,不關心停頓時間。想要低延遲,我們應該使用 CMS 收集器。CMS 是一款以擷取最短回收停頓時間為目标的收集器。
解決方案:
使用 CMS: