生产大文件下载导致 OOM,顺藤摸瓜拿下

戳上方蓝字“Java面试题精选”关注!

上周遇到了生产环境 OOM 的问题,找了一番之后基本定位了是大文件下载导致的问题,于是在网上搜罗了一番文章,下面分享一篇优质的解决方案,整个排查思路非常清晰,小白可以直接对照着来排查。

事故发生

上周五下午运营人员反馈,笔者所负责的后台系统从 14 点以后就卡卡的,虽然页面能够正常加载,但是一直处于数据加载中,数据也提交不了,怀疑笔者的系统有BUG,当听到运营人员的反馈我的第一反应是这不可能啊,这么简单的一个后台系统,还能出事故?

处理流程

  • 摘除其中一台服务器用于保留现场,其他服务器先重启,保证系统可用。
  • 下载GC日志,系统dump文件用于分析

GC log分析

系统启动参数,JVM内存分配:-Xmx4096m -Xms4096m -Xmn2560m

观察日志可知系统每隔 40S 发生一次 Full GC,耗时 200 毫秒,回收以后系统老年代占用也不大,才 15M,但是新生代回收完还有 2 个 G。

有点不可思议,竟然不是老年代塞满了数据,而是新生代塞满了数据。

初步推测是新生代数据要晋升到老年代,结果放不进去而引起的 Full GC。

生产大文件下载导致 OOM,顺藤摸瓜拿下

使用 MAT 对 Dump 文件进行分析

通过总图可以看出来目前系统内存占用超过 2 个 G:

生产大文件下载导致 OOM,顺藤摸瓜拿下

点击 Histogram 进行进一步分析,看出系统中占用最多的是byte[]

生产大文件下载导致 OOM,顺藤摸瓜拿下

点击List Objects进入income引用统计界面

生产大文件下载导致 OOM,顺藤摸瓜拿下

层层点开,发现byte[]被 ResponseEntity 对象所引用,且数量不小

生产大文件下载导致 OOM,顺藤摸瓜拿下

翻阅代码

1)在系统中找到唯一ResponseEntity有关的代码

生产大文件下载导致 OOM,顺藤摸瓜拿下

2)这代码看似没什么问题啊,这不是很正常的文件下载么???我去看看用户下载了啥,跑到目录文件查看一下下。

生产大文件下载导致 OOM,顺藤摸瓜拿下

我的天,用户下载的是一份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 或者是七牛云,绕过服务器下载

来源:飞天小牛肉
后端专属技术群

构建高质量的技术交流社群,欢迎从事编程开发、技术招聘HR进群,也欢迎大家分享自己公司的内推信息,相互帮助,一起进步!

文明发言,以交流技术职位内推行业探讨为主

广告人士勿入,切勿轻信私聊,防止被骗

生产大文件下载导致 OOM,顺藤摸瓜拿下

加我好友,拉你进群

原文始发于微信公众号(Java面试题精选):生产大文件下载导致 OOM,顺藤摸瓜拿下

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/256615.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
半码博客——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!