上周遇到了生産環境 OOM 的問題,找了一番之後基本定位了是大文件下載導致的問題,于是在網上搜羅了一番文章,下面分享一篇優質的解決方案,整個排查思路非常清晰,小白可以直接對照著來排查。
事故發生
上周五下午運營人員反饋,筆者所負責的後台系統從 14 點以後就卡卡的,雖然頁面能夠正常加載,但是一直處于數據加載中,數據也提交不了,懷疑筆者的系統有BUG,當聽到運營人員的反饋我的第一反應是這不可能啊,這麽簡單的一個後台系統,還能出事故?
處理流程
摘除其中一台服務器用于保留現場,其他服務器先重啓,保證系統可用。下載GC日志,系統dump文件用于分析GC log分析
系統啓動參數,JVM內存分配:-Xmx4096m -Xms4096m -Xmn2560m
觀察日志可知系統每隔 40S 發生一次 Full GC,耗時 200 毫秒,回收以後系統老年代占用也不大,才 15M,但是新生代回收完還有 2 個 G。
有點不可思議,竟然不是老年代塞滿了數據,而是新生代塞滿了數據。
初步推測是新生代數據要晉升到老年代,結果放不進去而引起的 Full GC。
使用 MAT 對 Dump 文件進行分析
通過總圖可以看出來目前系統內存占用超過 2 個 G:
點擊 Histogram 進行進一步分析,看出系統中占用最多的是byte[]
點擊List Objects進入income引用統計界面
層層點開,發現byte[]被 ResponseEntity 對象所引用,且數量不小
翻閱代碼
1)在系統中找到唯一ResponseEntity有關的代碼
2)這代碼看似沒什麽問題啊,這不是很正常的文件下載麽???我去看看用戶下載了啥,跑到目錄文件查看一下下。
我的天,用戶下載的是一份2.4G的大文件,代碼中FileUtils.readFileToByteArray(file) 的方式是把整個文件讀取到內存再輸出流裏寫入,此時內存不夠分配,又塞不進老年代,只能是 Full GC 了。
3)成功破案了,用戶下載了一份大文件,文件先加載到內存才往外寫,抹淚。。。。
解決方案
使用FileSystemResource
@GetMapping("/down")public ResponseEntity download(@RequestParam("uri") String uri) throws IOException { File file = new File(uri); if (!file.isFile()) { throw new 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; return new ResponseEntity<>(new FileSystemResource(file), headers, status);}使用緩存流,邊讀邊寫
@GetMapping("/down")public void download(@RequestParam("uri") String uri, HttpServletResponse response) throws IOException { File file = new File(uri); if (!file.isFile()) { throw new 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 或者是七牛雲,繞過服務器下載
來源:飛天小牛肉