點選「 IT碼徒 」, 關註,置頂 公眾號
每日技術幹貨,第一時間送達!
上周遇到了生產環境 OOM 的問題,找了一番之後基本定位了是大檔下載導致的問題,於是在網上搜羅了一番文章,下面分享一篇優質的解決方案,整個排查思路非常清晰,小白可以直接對照著來排查。
1
事故發生
上周五下午營運人員反饋,筆者所負責的後台系統從 14 點以後就卡卡的,雖然頁面能夠正常載入,但是一直處於數據載入中,數據也送出不了,懷疑筆者的系統有BUG,當聽到營運人員的反饋我的第一反應是這不可能啊,這麽簡單的一個後台系統,還能出事故?
2
處理流程
摘除其中一台伺服器用於保留現場,其他伺服器先重新開機,保證系統可用。
下載GC日誌,系統dump檔用於分析
3
GC log分析
系統啟動參數,JVM記憶體分配: -Xmx4096m -Xms4096m -Xmn2560m
觀察日誌可知系統每隔 40S 發生一次 Full GC,耗時 200 毫秒, 回收以後系統老年代占用也不大,才 15M,但是新生代回收完還有 2 個 G。
有點不可思議,竟然不是老年代塞滿了數據,而是新生代塞滿了數據。
初步推測是新生代數據要晉升到老年代,結果放不進去而引起的 Full GC。
4
使用 MAT 對 Dump 檔進行分析
透過總圖可以看出來目前系統記憶體占用超過 2 個 G:
點選 Histogram 進行進一步分析,看出系統中占用最多的是byte[]
點選List Objects進入income參照統計界面
層層點開,發現byte[]被 ResponseEntity 物件所參照,且數量不小
5
翻閱程式碼
1)在系統中找到唯一ResponseEntity有關的程式碼
2)這程式碼看似沒什麽問題啊,這不是很正常的檔下載麽???我去看看使用者下載了啥,跑到目錄檔檢視一下下。
我的天,使用者下載的是一份2.4G的大檔,程式碼中FileUtils.readFileToByteArray(file) 的方式是把整個檔讀取到記憶體再輸出流裏寫入,此時記憶體不夠分配,又塞不進老年代,只能是 Full GC 了。
3)成功破案了,使用者下載了一份大檔,檔先載入到記憶體才往外寫,抹淚。。。。
6
解決方案
使用FileSystemResource
@GetMapping("/down")
public ResponseEntity download(@RequestParam("uri") String uri) throws IOException {
File file = new File(uri);
if (!file.isFile()) {
thrownew ServiceException("檔不存在");
}
String filename = FilenameUtils.getName(uri);
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
HttpStatus status = HttpStatus.OK;
returnnew ResponseEntity<>(new FileSystemResource(file), headers, status);
}
使用緩存流,邊讀邊寫
@GetMapping("/down")
publicvoid download(@RequestParam("uri") String uri, HttpServletResponse response) throws IOException {
File file = new File(uri);
if (!file.isFile()) {
thrownew ServiceException("檔不存在");
}
String filename = FilenameUtils.getName(uri);
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
try (FileInputStream fileInputStream = new FileInputStream(file);
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(response.getOutputStream())) {
FileCopyUtils.copy(bufferedInputStream, bufferedOutputStream);
} finally {
// 使用的是try-with-resources
}
}
檔儲存到 oss 或者是七牛雲
將檔儲存到 oss 或者是七牛雲,繞過伺服器下載
— END —
PS:防止找不到本篇文章,可以收藏點贊,方便翻閱尋找哦。
往期推薦