目錄
二、MAT(Memory Analyzer Tool)分析
一 、問題描述
直接上圖,這個是之前在測試環境上發現的問題,導致整個服務崩了。(心裏暗喜,幸虧是測試環境啊,上線那不得...)從打印的日誌可以很清楚的知道啥原因,OOM嘛
java堆內存溢出原因:內存泄漏或者堆的大小設置不當引起的。對於內存泄露,需要通過內存監控軟件查找程序中的泄露代碼,而堆大小可以通過虛擬機參數-Xms,-Xmx等修改。
問題分析:
(1)這個服務在測試環境跑了這麼久,都沒出現這種情況,應該可以排除堆的大小設置不當的原因(正常操作應該是查看堆大小的設置情況進行排查問題,當時抖機靈了hhhh)
(2)在5分鐘前,我剛剛發了一個版本到測試環境,就出現這問題,感覺大概率是代碼問題
話不多說,趕緊分析一波代碼。但是,爲了假裝自己是一個比較在行的程序員,還是按流程辦事吧,先搞個dump文件,分析是哪裏出現問題了。
二、MAT(Memory Analyzer Tool)分析
1.下載地址
https://www.eclipse.org/mat/downloads.php
2.獲取dump文件
登錄服務器,進入到bin目錄下,用java自帶的jmap命令生成dump文件,命令如下:
./jmap -dump:format=b,file=/home/xxxxx/heap.hprof pid(進程id)
3.Leak Suspect報告
從報告中,可以清楚的看到 io/vertx/core/impl/TaskQueue 這個類中的一個屬性 java/util/LinkedList 類型的,佔用了60.83%內存,往上看,這個還是跟kafka消費有點關係。
4.Chart報告
這個圖也可以清晰的看到,java/util/LinkedList這個類型佔用了較高的內存,那這個肯定是問題的隱患所在嘛。
三、代碼問題
1.代碼邏輯很簡單,當有消息寫入的時候,則利用vertx框架的異步操作處理邏輯。
vertx.executeBlocking(future->{
/**
* 業務代碼邏輯
*/
},null);
2.查看這個異步操作的執行過程
(1)VertxInternal 接口 Vertx 接口;VertxImpl 類 實現了 VertxInternal 接口,並重寫了 executeBlocking方法
(2)executeBlocking方法分析
兩個接口的區別之處在於,多了個order參數(是否有序),我的代碼是調用了下面的接口,默認是有序執行。我們在仔細的品一品上面這個方法的實現。ContextImpl 、ContextImpl、ContextImpl 這個類不就是打印的日誌中報oom的類嘛是,說明距離真相已經很近了。
我們在仔細的看一下這個方法的具體實現,裏面有這麼一段代碼。這個queue就是我們之前報告分析中的TaskQueue。當我們設置了order=true的時候,會創建一個TaskQueue,用於按序存放要執行的任務;同時,由於是按序執行任務,vertx框架只會創建一個工作線程來處理業務邏輯,用於保證有序執行任務。在測試環境只有一臺機器,也就是隻有一個消費者,而我們的生產者大概是一分鐘產生2w條數據,導致TaskQueue中的 tasks 添加了較多的任務而出現OOM。
private final LinkedList<Task> tasks = new LinkedList<>();
- ContextImpl 類中的executeBlocking方法
if (queue != null) {
queue.execute(command, exec);
} else {
exec.execute(command);
}
- TaskQueue類中的execute方法
public void execute(Runnable task, Executor executor) {
synchronized (tasks) {
tasks.add(new Task(task, executor));
if (current == null) {
current = executor;
executor.execute(runner);
}
}
}
四、問題解決
- 將執行異步操作的過程設置會無序的,這樣的話vertx框架會創建一個線程池,用於執行任務
- 自己創建一個固定大小的線程池用於執行任務,類似方案(1)