作者 | Mason MA
【Arthas 官方社區正在舉行征文活動,參加即有獎品拿哦~點擊投稿】
Arthas 是個不錯的工具,這里要再安利一波,當然整個過程還用到了其他工具,如 MAT、YourKIT(這個是付費的),結合起來使用更加便於發現和解決問題。期間還和開發大佬多次溝通,分別獲取了不同的信息。
一鍵安裝並啟動 Arthas
- 方式一:通過 Cloud Toolkit 實現 Arthas 一鍵遠程診斷
Cloud Toolkit 是阿里雲發布的免費本地 IDE 插件,幫助開發者更高效地開發、測試、診斷並部署應用。通過插件,可以將本地應用一鍵部署到任意服務器,甚至雲端(ECS、EDAS、ACK、ACR 和 小程序雲等);並且還內置了 Arthas 診斷、Dubbo工具、Terminal 終端、文件上傳、函數計算 和 MySQL 執行器等工具。不僅僅有 IntelliJ IDEA 主流版本,還有 Eclipse、Pycharm、Maven 等其他版本。
推薦使用 IDEA 插件下載 Cloud Toolkit 來使用 Arthas:http://t.tb.cn/2A5CbHWveOXzI7sFakaCw8
- 方式二:直接下載
地址:https://github.com/alibaba/arthas。
現象
- 建索引的后台應用,感覺用不到那么大內存,現在用到了並且隔兩天就會 oom,需要重啟;
- 有全量數據和增量數據,OOM 大多發生在全量數據寫入階段,且 OOM 基本都在凌晨首次觸發全量數據更新時出現;
- 業務應用使用了 G1 收集器(高級高級...)。
內心 OS:糟糕,G1 還不熟可怎么辦,先想個辦法把大佬們支開,我自己再慢慢研究。
我還有點別的事兒,我等會再看
苟膽假設
在現有掌握的信息下判斷,大膽假設一下,反正猜錯了又不會賠錢。
- 是否是因為全量數據寫入,超過了堆的承載能力,導致了 OOM?
- 業務是否有 static 容器使用不當,一直沒回收,一直往里 put 元素,所以需要兩天 OOM 一次?
- 內存不夠,是哪些對象占用最多,先找出來看看?
- 有沒有大對象?
發抖求證
基本信息
進程啟動參數
-Xms12g
-Xmx12g
-XX:+UseG1GC
-XX:InitiatingHeapOccupancyPercent=70
-XX:MaxGCPauseMillis=200
-XX:G1HeapWastePercent=20
-XX:+PrintAdaptiveSizePolicy
-XX:+UseStringDeduplication
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCApplicationStoppedTime
-XX:+PrintTenuringDistribution
-Xloggc:/home/search/fse2/gc.log
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=2
-XX:GCLogFileSize=512M
-XX:+UnlockDiagnosticVMOptions
-XX:+PrintNMTStatistics
-XX:NativeMemoryTracking=summary
可以看到,使用了 G1 收集器,這個之前做業務開發的時候還不常見呢,畢竟是為大內存打造的追求低延遲的垃圾回收器。關於 G1 收集器的一些基本特性,大家可以去搜集一些資料。大概主要包括以下幾項:
- Region 分區機制
- SATB,全稱是 Snapshot-At-The-Beginning,由字面理解,是 GC 開始時活着的對象的一個快照
- Rset,全稱是 Remembered Set,是輔助 GC 過程的一種結構,空間換時間思想
- Pause Prediction Model 即停頓預測模型
從 G1 的這些屬性來看,它期望做到減少人為操作調優,實現自動化的調優(說到這里,感覺本次出現的 OOM 似乎跟垃圾收集器本身關聯並不大,並不是因為業務量大堆內存不夠用,可能根本原因在代碼邏輯層面),並且適應在硬件成本降低,大內存堆逐漸變多的場景(后面還有 ZGC 和 Shenandoah,同樣是可以管理超大內存並停頓很低的神奇垃圾收集器)。
已經有GC日志了,那先看看日志,發現一處異常:
#### 這里Heap回收的還只是300多M內存
[Eden: 316.0M(956.0M)->0.0B(1012.0M) Survivors: 48.0M->44.0M Heap: 359.2M(12.0G)->42.0M(12.0G)]
[Times: user=0.31 sys=0.01, real=0.05 secs]
2020-06-09T02:59:23.489+0800: 2020-06-09T02:59:23.489+0800: 35.922: Total time for which application threads were stopped: 0.0578199 seconds, Stopping threads took: 0.0000940 seconds
35.922: [GC concurrent-root-region-scan-start]
......
......
......
#### 這個點Heap回收的就是11G內存了
[Eden: 724.0M(1012.0M)->0.0B(540.0M) Survivors: 44.0M->72.0M Heap: 11.5G(12.0G)->355.6M(12.0G)]
[Times: user=1.51 sys=0.07, real=0.26 secs]
2020-06-09T03:12:36.329+0800: 828.762: Total time for which application threads were stopped: 0.2605902 seconds, Stopping threads took: 0.0000600 seconds
初次調試
增加 -XX:G1ReservePercent 選項的值,為“目標空間”增加預留內存量。
減少 -XX:InitiatingHeapOccupancyPercent
提前啟動標記周期
懷疑在 GC 的當時,有大量數據全量寫入,內存還沒回收完,寫進大量對象,導致了 OOM,於是調了啟動周期,占比由 70 下降到55,提前觸發,預留更多堆空間。
GC 變得頻繁了,但是內存占用過大的問題並沒有得到解釋。並且是否會再次在凌晨出現 OOM 還需要等。。。於是還要繼續看看有沒有別的問題。
繼續挖掘
有疑點沒有解答,肯定是要繼續挖掘的,抄起鍵盤就是干,一頓操作猛如虎。
Arthas
不知道哪位已經安裝了,先拿來用用看吧,大概用到了以下幾個命令:
dashboard
看看基本情況,線程數,堆大小,老年代大小,使用占比情況。首次看到這里時,eden 和 old 區的占用都挺高的, 70~80% 吧(當時忘了截圖)。內存使用率比較高。
thread
看看線程數有沒有異常,線程數正常,只是找出了資源占用比較高的線程,其中一個線程在后面其他信息統計中還會出現:
YJPAgent-Telemetry
ctrl-bidsearch-rank-shard1
YJPAgent-RequestListener
ctrl-bidsearch-rank-shard1
居然有兩個線程是 YourKit 監控工具的 agent,看來這個監控工具對性能影響還挺大的。
profiler
分別采集一下內存和 CPU 的火焰圖數據:
# 內存
profiler start --event alloc
###
profiler stop
# 默認CPU
profiler start
###
profiler stop
- CPU
- 內存
從 CPU 火焰圖看到,G1 收集器線程居然占了一半資源,可能采集當時正在進行 GC,不過,除此之外,基本都能定位到是一個叫 IncrementIndexService
的類,使用了比較多的 CPU 和內存資源。
如果業務代碼真的存在缺陷,那么一定在這個流程里,后來經過溝通,發現這個應用處理任務主要入口的確是在這里面。先持有保留意見。
thread 看到的線程和 profiler 看到的 class,都最終定位到是同一個業務流程。
開始驗證之前的猜想:
1. 是否在全量數據寫入的時候有大量對象涌入內存?
計算了一些業務代碼獲取數據的量,元數據大約也就在 1.3G 左右,就算全量寫入,也不應該占用 12G 的堆內存,所以猜測全量數據寫入時,代碼邏輯可能有什么缺陷,導致 1.3G 的原數據被封裝成遠大於 1.3G 的對象了。
2. 是否有 static 容器?
有,但是經過 watch 觀察,沒有發現容器只 put 不 remove 的情況,命令如下:
watch com.xxx.classname method "{params,target}" -x 3
3. 有沒有大對象?
對於 G1,默認將堆分成 2048 個 Region,12G 的堆,一個 Region 是 6M,超過 3M 才是大對象。
jmap histo 30123
至少輸出的數據中,大對象不是 G1 定義的大對象。
MAT
既然沒什么發現,就把堆 dump 出來吧。如果不想或者不能 dump,也可以用 jmap histo 查看內存占用,優點是不用 dump,缺點是通常不能很好的和業務代碼之間建立關聯。
警告:jmap 或者 Arthas 的 heapdump 操作之前一定要斷開流量。好在我們這個服務沒有線上流量,建索引有延遲,可能短暫影響搜索體驗。
dump 出來之后,發現有 7 個 G,這么大的文件一般很難傳到本地來分析了,於是用 MAT,占用服務器 1 個 G 內存進行分析,分析完成的結果下載到本地。
線程名稱和之前發現的信息吻合,點開 detail 有驚喜。
一串奇怪的字符串,有點像 XML,好像在拼裝着什么,於是找到業務大佬請教,發現的確有拼裝 solrDocument 的邏輯,而且,經過 YourKit 輸出部分對象值的片段,可以發現有大部分是在重復拼裝,大概意思如下:
<xml>
...
<country>CA</country>
<country>CA</country>
<country>CA</country>
<country>CA</country>
<country>CA</country>
...n次重復
<country>US</country>
<country>US</country>
<country>US</country>
<country>US</country>
<country>US</country>
<country>US</country>
...n次重復
<country>UK</country>
<country>UK</country>
...
</xml>
代碼邏輯修改其實比較簡單了,業務開發大佬們比較熟悉業務流程,很快就就有了修改方案。
調整之后發現,內存使用量下降了很多。
直接降到了 4-5G 左右,如果是這樣的話,即便全量數據寫入時,正在做垃圾回收,應該還是夠用的。但是感覺這個代碼邏輯里面,應該還有優化空間,不過,先解決問題,優化可以做下一步操作。
復盤
無論從哪個工具得出的數據,都顯示 IncrementIndexService
這個類的邏輯可能有問題,最終問題的根本和 G1 參數設置好像也沒什么關系,代碼邏輯缺陷才是根源,再擴大內存可能或者調整 JVM 參數,也只能將故障緩解,但是不能解決。
-
從進程到線程到代碼
-
獲取 JVM 基本信息,收集器,啟動參數等信息
-
查看現有的日志,GC 日志,業務日志
-
溝通業務場景,了解輸入數據規模等等
-
猜想可能存在的原因,大膽的猜
-
使用工具(Arthas、MAT、YourKit、JDK 自帶命令等等)挖掘信息,火焰圖、耗能線程、線程棧 堆dump,占比分析 大對象 ...
-
結合數據重新梳理,發現業務代碼的關聯和可能存在的缺陷,可以嘗試調整參數
-
業務代碼若有 bug,修復 bug
Arthas 征文活動火熱進行中
Arthas 官方正在舉行征文活動,如果你有:
- 使用 Arthas 排查過的問題
- 對 Arthas 進行源碼解讀
- 對 Arthas 提出建議
- 不限,其它與 Arthas 有關的內容
歡迎參加征文活動,還有獎品拿哦~點擊投稿
“阿里巴巴雲原生關注微服務、Serverless、容器、Service Mesh 等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,做最懂雲原生開發者的公眾號。”