1. 程式人生 > >tsync堆外記憶體溢位排查經過

tsync堆外記憶體溢位排查經過

一、發生得問題
tsync服務總是莫名得宕機,java程序被莫名其妙的消失了。

二、查詢問題
當時看了系統日誌:
sudo -u admin dmesg|grep -A20 kill
在這裡插入圖片描述
screenshot
發現是oom了,記憶體不足被系統kill掉了。
當時懷疑有可能是堆內記憶體溢位,檢視監控,系統實體記憶體:
在這裡插入圖片描述

發現物理記憶體確實使用了100%。
繼續看java堆記憶體:
screenshot
發現java堆內記憶體很正常old區就一直沒有滿過。
這個時候懷疑可能堆外記憶體溢位(此後一段時間也注意看了下java程序RES一直在增長直到漲到實體記憶體又會被系統kill掉)。後來dump出了當時記憶體要漲滿時的堆記憶體快照,上傳到了zprofiler裡,所有記憶體使用都很正常。所以已經確定是堆外記憶體溢位了。
java堆外溢位最可能得是DirectMemory,正好tsync就會用到nio,所以懷疑因為佔用了DirectMemory,因為old區一直沒有回收所以有可能是DirectMemory的引用物件一直沒有回收導致相應的堆外記憶體沒回收,當時又手動呼叫了一下fullgc,因為fullgc一直沒有發生過,看看記憶體能不能回收:jmap -histo:live 123345 ,結果java程序的RES還是沒有降下來,也就是堆外記憶體還是沒有回收,檢視jvm引數發現有個引數DisableExplicitGC 。這個引數禁止了System.gc()方法導致的fullgc,所以我手動回收應該也沒有用,如果是這個問題得話定義下MaxDirectMemorySize,並把DisableExplicitGC禁止去掉就應該解決問題了,因為fullgc代價太大查了下資料再增加ExplicitGCInvokesConcurrent 引數,當堆外記憶體到達MaxDirectMemorySize時不進行fullgc,而是進行cms gc,當時還覺得問題能解決。不過還是看了下所有程式碼,看到用到nio的bytebuffer都是已經傳得false即禁用DirectMemory,又看了下zprofiler裡的DirectByteBuffer才幾十兆:
screenshot


所以確定應該不是這個問題。
又和pe商量了下,pe給安裝了個perftools,列印程式使用的堆外記憶體快照,跑了一個星期,找出了佔用最大得java得native得方法
screenshot
Java_sun_security_ec_ECKeyPairGenerator_generateECKeyPair這個值一直從200多m逐漸上升到1G多。而且對比多個不同時期的檔案佔用記憶體一直再上漲都沒有釋放過……,證明不是瞬發流量突增引起的沒有及時釋放,是根本就因為不明原因沒有釋放。

三、定位引用源頭
這個native方法是jdk裡的,為了查明是業務中哪塊程式碼用的這個類方法,在idea裡搜尋,沒有找到,懷疑是二方包裡用的,然後申請了oss的賬號和祕鑰在一臺beta機器上安裝btrace 來進行分析,btrace指令碼如下:
screenshot


在beta機器上執行./btrace 123345 TraceMemory.java,觀察了很久沒有打印出呼叫棧資訊,懷疑是指令碼寫錯了把指令碼的類和方法改為別的再執行是能成功列印棧資訊的,試了其他得也都可以,想到可能是這個包裡的類不能被btrace跟蹤,請教了下畢玄說也可能是sun包下得不能被trace。沒有辦法這條路暫時不通,我又想了個辦法在日常環境debug那個程式碼碰碰運氣看還有沒有呼叫,正好就debug上了看idea的呼叫棧如下:
screenshot
原來悟空jar包再引用這個類和方法,原來也懷疑呼叫的華為jar包應該也有,因為它使用了https,看了下異常日誌確實找到了也在使用相關類如下:
screenshot
到這估計已經都找到了引用源頭。

四、現在的問題:
為什麼這個native方法會記憶體洩露而不釋放,什麼樣的觸發條件導致了這個問題?線上使用的jdk版本是1.7的