運維人員反饋一個容器化的java程序每跑一段時間就會出現OOM問題,重啓後,間隔大概兩天後復現。java
因爲是容器化部署的程序,登上主機後使用docker logs ContainerId查看輸出日誌,並無發現任何異常輸出。 使用docker stats查看容器使用的資源狀況,分配了2G大小,也沒有發現異常。docker
打算進入容器內部一探究竟,先使用docker ps 找到java程序的ContainerId
,再執行docker exec -it ContainerId /bin/bash進入容器。進入後,本想着使用jmap、jstack 等JVM分析命令來診斷,結果發現命令都不存在,顯示以下:api
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分析命令,參照用法以下:bash
Usage: docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|- docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH [flags]
有了JVM工具,咱們就能夠開始分析咯。服務器
經過jstat查看gc狀況數據結構
bin/jstat -gcutil 1 1s
看樣子沒有什麼問題,full gc也少。再看一下對象的佔用狀況,因爲是容器內部,進程號爲1,執行以下命令:運維
bin/jmap -histo 1 |more
發現ByteBuffer對象佔用最高,這是異常點一。
eclipse
bin/jstack -l 1 > thread.txt
下載快照,這裏推薦一個在線的線程快照分析網站。工具
https://gceasy.io
上傳後,發現建立的線程近2000個,且大可能是TIMED_WAITING狀態。感受逐漸接近真相了。 點擊詳情發現有大量的kafka-producer-network-thread | producer-X 線程。若是是低版本則是大量的ProducerSendThread線程。(後續驗證得知),能夠看出這個是kafka生產者建立的線程,以下是生產者發送模型:網站
根據生產者的發送模型,咱們知道,這個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後觀察,發現線程數一直增加,使用內存也在逐漸增長,具體狀況以下圖:
分析到這裏,基本肯定了,應該是業務代碼中循環建立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) 老生代內存不足的時候