背景:
Response 获取的对象为:ContentCachingResponseWrapper
页面点击下载文件,后台报错如下:
org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: Java heap space
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1076) ~[spring-webmvc-5.3.2.jar:5.3.2]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:961) ~[spring-webmvc-5.3.2.jar:5.3.2]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) [spring-webmvc-5.3.2.jar:5.3.2]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) [spring-webmvc-5.3.2.jar:5.3.2]
demo代码如下:
public static void dowlondFile(String filePath, HttpServletResponse response, HttpServletRequest request) {
File file = new File(filePath);
String fileName = "TALENT_RES_download.zip";
if (file.exists()) {
dowlondFile(file,fileName,response);
}
}
/**
* 下载文件
* @param file 要下载的文件名
* @param fileName 下载下来的文件命名
* @param response (ContentCachingResponseWrapper 类型)
*/
public static void dowlondFile(File file, String fileName, HttpServletResponse response){
try(InputStream inputStream =new FileInputStream(file) ) {
response.setCharacterEncoding("utf-8");
response.setContentType("multipart/form-data");
response.setHeader("Content-disposition",
"attachment; filename="+ java.net.URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
int length;
// OutputStream os = response.getOutputStream();
OutputStream os = ((ContentCachingResponseWrapper)response).getResponse().getOutputStream();
byte[] b = new byte[1024];
while ((length = inputStream.read(b)) > 0) {
os.write(b, 0, length);
}
os.flush();
os.close();
}catch (IOException e){
log.error("Execute dowlondFile error.",e);
}
}
分析:
首先打印堆栈:
打印堆栈方式:
1. VM 参数设置:-XX:+HeapDumpOnOutOfMemoryError
当出现OOM时候,会打印堆栈
2. jmap -dump:format=b,file=fileName.hprof pid
备注:pid 为要打印堆栈进程的进程id.
通过MAT分析堆栈信息:
如下:(发现主要出问题的对象为:FastByteArrayOutputStream
点击Details
点击红色框里面的See stacktrace.
解决措施:
通过堆栈,发现问题出在下图:这边HttpServletResponse 类型为ContentCachingResponseWrapper
使用FastByteArrayOutputStream读取数据到内存,要下载的文件大小 > 可用内存大小。
而 CoyoteOutputStream 是读取数据到OutputBuffer。若超过限制,则flush出去,然后buffer清零。
因此将 OutputStream os = response.getOutputStream()
改为:OutputStream os = ((ContentCachingResponseWrapper)response).getResponse().getOutputStream()
经测试,未出现OOM。