问题描述
公司项目跟第三方公司对接数据,第三方提供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);
}
}