RestTemplate下载第三方文件,文件太大导致OOM

249 阅读2分钟

问题描述

公司项目跟第三方公司对接数据,第三方提供url,通过RestTemplate下载文件。

有一天,项目现场反馈系统频繁死机。查看日志后发现系统出现过频繁的OOM。为了排查导致OOM的具体原因,修改了JVM启动参数:

-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=./java_pid<pid>.hprof
-XX:OnOutOfMemoryError="< cmd args >;< cmd args >"
-XX:+UseGCOverheadLimit
  • HeapDumpOnOutOfMemoryError 指示 JVM 在遇到 OutOfMemoryError 错误时将 heap 转储到物理文件中。
  • HeapDumpPath 表示要写入文件的路径; 可以给出任何文件名; 但是,如果 JVM 在名称中找到一个 <pid>标记,则当前进程的进程 id 将附加到文件名中,并使用.hprof格式
  • OnOutOfMemoryError 用于发出紧急命令,以便在内存不足的情况下执行; 应该在 cmd args 空间中使用适当的命令。例如,如果我们想在内存不足时重启服务器,我们可以设置参数: -XX:OnOutOfMemoryError="shutdown -r" 。
  • UseGCOverheadLimit 是一种策略,它限制在抛出 OutOfMemory 错误之前在 GC 中花费的 VM 时间的比例

使用MAT分析dump文件,发现是因为下载文件时文件内容太大,我们下载文件时是加载全部内容到内存中,再写到硬盘。但是文件太大,内存直接撑爆了。

MAT介绍(Memory Analyzer Tool)

MAT(Memory Analyzer Tool)是一款快速便捷且功能强大丰富的 JVM 堆内存离线分析工具。其通过展现 JVM 异常时所记录的运行时堆转储快照(Heap dump)状态(正常运行时也可以做堆转储分析),帮助定位内存泄漏问题或优化大内存消耗逻辑。

在遇到 OOM 和 GC 问题的时候,我一般会首选使用 MAT 分析 dump 文件在,这也是该工具应用最多的一个场景。

解决方案

使用ResponseExtractor将远程服务器中的文件直接转成流存到文件中,而不放到内存。

代码示例

public class FileDownloadUtil {

  private FileDownloadUtil() {
    throw new IllegalStateException("Utility class");
  }

  /**
   * 远程服务下载文件
   * @param url
   * @param filePath
   * @param restTemplate
   */
  public static void downloadFile(String url, String filePath, RestTemplate restTemplate) {

    //定义请求头的接收类型
    RequestCallback requestCallback = request -> request.getHeaders()
      .setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));

    // getForObject会将所有返回直接放到内存中,使用流来替代这个操作
    ResponseExtractor<Void> responseExtractor = response -> {
      Files.copy(response.getBody(), Paths.get(filePath));
      return null;
    };
    restTemplate.execute(url, HttpMethod.GET, requestCallback, responseExtractor);
  }
}