線上內存泄露排查
更多博文請關注:https://blog.bigcoder.cn
一. 排查過程
前不久測試環境一直無緣無故的掛掉,這可苦了我們一線開發人員,每次測試都得把掛掉服務全部起起來。面對幾十個微服務模塊,我想大家看到這樣的場景內心也是一萬個草泥馬飛過….

硬著頭皮把代碼寫完了,但是大規模的服務宕機顯然不正常,看了一波還是熱乎的日志,發現是服務器OOM了,順道看了一下JVM配置,居然沒有任何配置,看到這里嘴角絲絲上揚,覺得自己破案了。
隨即給核心服務都加上了JVM參數:
JAVA_OPTS: "-server -Xmx256m" #因為是測試環境對擴容抖動并不敏感,所以沒有設置Xms,這樣可以節省一點測試環境資源
改完配置,我覺的自己頭不疼了,自己又行了。
第二天上班發現,測試環境還是掛了

帶著滿臉的問號,看了一波日志,結果還是OOM,但是掛的服務明顯變少了。這一波我開始懷疑是不是測試環境應用內存給的確實太少了(線上是1GB),我把堆內存改成了512M:
JAVA_OPTS: "-server -Xmx512m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/heapdump.hprof"
這一波我把HeapDumpOnOutOfMemoryError參數帶上了,當它OOM的時候將堆內存Dump下來。
抱著懷疑的態度,連續觀察了測試環境幾天的狀態,沒有出現OOM掛掉的情況。到這里,我覺得我真的行了。
你以為這就完了?故事當然沒有這么簡單
過了大概半個月,又有應用出現了OOM。這一次比較好,因為保留了案發現場,可以分析一下移除原因,利用JProfile打開hprof文件,把我驚呆了:

com.dianping.cat.message.io.TcpSocketSender占用了94%的堆內存,分析Class占用發現是ArrayBlockingQueue持有了太多String對象導致的。

二. 探尋源碼
因為測試環境中我們是沒有部署Cat服務器,猜測是因為連接不到Cat的服務器導致消息堆積,隨著時間的推移,堆積消息到達上限產生了OOM。
研究了一下Cat源碼,發現代碼中確實有這樣的邏輯(省略了大部分無關代碼):
public class TcpSocketSender implements Task, MessageSender, LogEnabled {
private MessageQueue m_queue = new DefaultMessageQueue(SIZE);
...
@Override
public void run() {
m_active = true;
while (m_active) {
processAtomicMessage();
processNormalMessage();
}
processAtomicMessage();
while (true) {
// 隊列中獲取消息
MessageTree tree = m_queue.poll();
if (tree != null) {
// 獲取發送管道
ChannelFuture channel = m_channelManager.channel();
if (channel != null) {
// 發送流程
sendInternal(channel, tree);
} else {
// 若未獲取到發送管道,則將消息在塞回隊列中(罪魁禍首)
offer(tree);
}
} else {
break;
}
}
}
}
問題似乎找到了,解決方案有兩個一個是將測試環境應用中的Cat disabled掉,另一種是在測試環境中部署一個Cat服務。
三. 一絲絲的疑惑
為什么我將應用服務堆內存從256M擴大到512M,效果卻從不到一天就OOM,擴大到了將近半個月才OOM呢?
仔細想了一下,是因為我們測試環境的應用每天凌晨都會自動deploy一次,這樣就掩蓋了Cat內存泄露的問題。應用每天還沒有達到OOM閾值就被重啟了。而半個月后又出現的這次OOM,是因為最近幾天Jenkins授權問題拉取Groovy腳本失敗,并沒有執行真正的觸發邏輯。這就導致掩蓋的內存泄露又暴露出來了。
一切想通后,心情舒暢,我覺得自己真的行了

浙公網安備 33010602011771號