版權聲明:本文為博主原創文章,轉載請注明出處。
記一次壓測時Java內存泄漏問題的發現過程(2017-08-14)
【前篇】
①20170811進行A系統與B系統之間的會話功能進行壓測,加上腳本准備期間的聊天消息,預計累計聊天30w+條消息;
②20170814原計划加大量對會話功能進行壓測,情況如下;
【應用表現】
①B系統前台打開報錯“504”;

②查看后台應用CPU情況,CPU利用率高達700+%(8核);

③查看后台內存情況,持續FullGC,且一次FullGC的時長在9s左右,從這里可以粗略定位CPU高的原因是內存GC問題導致;

【查看應用JVM配置】
①請教B開發團隊,loader提到應該不是JVM配置引起的問題;

【嘗試進行分析】
①嘗試使用jvisualvm進行“堆 dump”,但是因為沒有內存了所以jvisualvm連接后卡死(之前測試可以正確連接並顯示JVM情況);
②使用jmap命令“jmap -dump:format=b,file=heap.hprof pid”進行dump,dump文件有16G(修改mat配置,無奈客戶端硬件差);
③嘗試shutdown應用后重啟,無法shutdown;最后使用“kill -9 pid”暴力解決無法shutdown的情況,后重啟應用;
【重啟后情況】
①使用jvisualvm查看堆內存使用情況,表現為“堆內存持續上升”;

②重啟1小時后,dump文件進行分析,其中“java.util.concurrent.LinkedBlockingQueue$Node”占用內存高達1G,基本可以判斷存在“內存泄漏”;

“com.best.oasis.B.common.entity.messageTransship.MessageTransship”對象151MB,且有160w個MessageTransship對象;

③B開發Review代碼:原因所在:線程池中等待執行的任務隊列存在內存泄漏的問題;
正常情況:
A應用服務器發送消息給B服務器后,B服務器接收消息后將該消息存於中間表B_messagetransship中,同時將該消息轉發給B客服端,B客服端接收消息並對該條消息進行ack,ack成功后刪除B_messagetransship中的該條消息。為了防止消息丟失,B有一個定時重發job,用於每隔5s將B_messagetransship表中的消息再次推送一次;
異常情況:
1.A服務器發送給B服務器的消息存在於B_messagetransship表中后(此時狀態為“PENDING_SEND”),因為網絡/B客戶主動退出等問題,致使B客戶端並未收到來自B服務器的該消息,則該消息的狀態被置為“SEND_FAILED”存在表B_messagetransship中;
2.A服務器發送給B服務器的消息,B客服端正確收到,但是B客服端發送的ACK請求返回失敗,則該消息的狀態被置為“PENDING_ACK”存在表B_messagetransship中;
失敗消息定時重發實現邏輯:
每隔5s從B_messagetransship中逐個取出失敗的消息記錄,以鏈式隊列的形式鏈接在等待執行的任務隊列中,若5s內該消息被線程處理且推送狀態為成功,則刪除數據庫表中該消息記錄;若5s內該消息被線程處理但推送狀態為失敗,則數據庫表中的該條消息記錄保持不變;若5s內該消息並未來得及被線程處理,下一次定時重發任務觸發時,該消息會保留第二個拷貝在待處理任務隊列中,以此類推;

bug發現的誘因:
B_messagetransship表失敗推送的消息量比較大,B_messagetransship表11w+條數據,失敗消息量大的原因:
①11907條“再見”,狀態為:SEND_FAILED
產生原因:B客戶端對話完畢未接收到再見語,就發起了“{"type":"close","sid":"${sid}"}”的請求,該現象在實際中也可能產生;
②17120條“很高興為您服務”,狀態為:PENDING_ACK
產生原因:壓測腳本未對“很高興為您服務”消息進行ack;
③剩余的8w+條,為A發送給B的對話消息,推測是在腳本准備期間產生的數據;
開發下期優化思路:
①為B_messagetransship表中的消息增加生存時長,若超時則直接刪除;
②限制待執行任務隊列中messageTransship對象的數量,達到一定個數則不再從B_messagetransship中獲取;
測試腳本修改:
①增加對“開始語”與“結束語”消息的ack;
