一次容器化springboot程序OOM問題探險

背景

運維人員反饋一個容器化的java程序每跑一段時間就會出現OOM問題,重啓後,間隔大概兩天後復現。java

問題調查

一查日誌

因爲是容器化部署的程序,登上主機後使用docker logs ContainerId查看輸出日誌,並無發現任何異常輸出。 使用docker stats查看容器使用的資源狀況,分配了2G大小,也沒有發現異常。docker

二缺失的工具

打算進入容器內部一探究竟,先使用docker ps 找到java程序的ContainerId
,再執行docker exec -it ContainerId /bin/bash進入容器。進入後,本想着使用jmap、jstack 等JVM分析命令來診斷,結果發現命令都不存在,顯示以下:segmentfault

bash: jstack: command not found
bash: jmap: command not found
bash: jps: command not found
bash: jstat: command not found

忽然意識到,可能打鏡像的時候使用的是精簡版的JDK,並無這些jVM分析工具,可是這仍然不能阻止咱們分析問題的腳步,此時docker cp命令就派上用場了,它的做用是:在容器和宿主機之間拷貝文件。這裏使用的思路是:拷貝一個新的jdk到容器內部,目的是爲了執行JVM分析命令,參照用法以下:api

Usage:  docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-
        docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH [flags]

有了JVM工具,咱們就能夠開始分析咯。bash

三查GC狀況

經過jstat查看gc狀況服務器

bin/jstat -gcutil 1 1s

file

看樣子沒有什麼問題,full gc也少。再看一下對象的佔用狀況,因爲是容器內部,進程號爲1,執行以下命令:數據結構

bin/jmap -histo 1 |more

發現ByteBuffer對象佔用最高,這是異常點一。
file運維

四查線程快照狀況
  • 經過jstack查看線程快照狀況。
bin/jstack -l 1 > thread.txt

下載快照,這裏推薦一個在線的線程快照分析網站。eclipse

https://gceasy.io

file

上傳後,發現建立的線程近2000個,且大可能是TIMED_WAITING狀態。感受逐漸接近真相了。 點擊詳情發現有大量的kafka-producer-network-thread | producer-X 線程。若是是低版本則是大量的ProducerSendThread線程。(後續驗證得知),能夠看出這個是kafka生產者建立的線程,以下是生產者發送模型:工具

file

根據生產者的發送模型,咱們知道,這個sender線程主要作兩個事,一是獲取kafka集羣的Metadata共享給多個生產者,二是把生產者送到本地消息隊列中的數據,發送至遠端集羣。而本地消息隊列底層的數據結構就是java NIO的ByteBuffer。

這裏發現了異常點二:建立過多kafka生產者。

因爲沒有業務代碼,決定寫一個Demo程序來驗證這個想法,定時2秒建立一個生產者對象,發送當前時間到kafka中,爲了更好的觀察,啓動時指定jmx端口,使用jconsole來觀察線程和內存狀況,代碼以下:

nohup java -jar -Djava.rmi.server.hostname=ip 
 -Dcom.sun.management.jmxremote.port=18099
 -Dcom.sun.management.jmxremote.rmi.port=18099
 -Dcom.sun.management.jmxremote.ssl=false
 -Dcom.sun.management.jmxremote.authenticate=false -jar
 com.hyq.kafkaMultipleProducer-1.0.0.jar   2>&1 &

鏈接jconsole後觀察,發現線程數一直增加,使用內存也在逐漸增長,具體狀況以下圖:

file

故障緣由回顧

分析到這裏,基本肯定了,應該是業務代碼中循環建立Producer對象致使的。
在kafka生產者發送模型中封裝了 Java NIO中的 ByteBuffer 用來保存消息數據,ByteBuffer的建立是很是消耗資源的,儘管設計了BufferPool來複用,但也經不住每一條消息就建立一個buffer對象,這也就是爲何jmap顯示ByteBuffer佔用內存最多的緣由。

總結

在平常的故障定位中,多多使用JDK自帶的工具,來幫助咱們輔助定位問題。一些其餘的知識點:
jmap -histo顯示的對象含義:

[C 表明  char[]
[S 表明 short[]
[I 表明 int[]
[B 表明 byte[]
[[I 表明 int[][]

若是導出的dump文件過大,能夠將MAT上傳至服務器,分析完畢後,下載分析報告查看,命令爲:

./mat/ParseHeapDump.sh active.dump  org.eclipse.mat.api:suspects
org.eclipse.mat.api:overview org.eclipse.mat.api:top_components

可能儘快觸發Full GC的幾種方式

1) System.gc();或者Runtime.getRuntime().gc();

2 ) jmap -histo:live或者jmap -dump:live。
這個命令執行,JVM會先觸發gc,而後再統計信息。
3) 老生代內存不足的時候
歡迎關注vx公衆號 【俠夢的開發筆記】
相關文章
相關標籤/搜索