[SpringBoot/SpringMVC]从Webapp下载一个大文件出现java.lang.OutOfMemoryError: GC overhead limit exceeded怎么办?
本文示例工程下载:https://files.cnblogs.com/files/xiandedanteng/WebFileDownload20191026.rar
制作一个Webapp,让其中一个网页提供下载链接,以使用户能下载本地文件或是临时生成的文件,这些都不是难事,网上也有很多既存的解决方案。
但是,这个问题难点在,但生成文件过大时,产生java.lang.OutOfMemoryError异常怎么办?有人提出修改JVM内存参数,如-Xms<min>m -Xmx<max>m方式,但终究是治标不治本的方法,如果下载数据量又超过设定的上限呢?
其实问题的本质是:在为大数据生成准备过程中,大量对象产生了来不及释放,因为还需要在接下来的步骤中使用,所以驻留在内存中,内存越积越多,终究导致java.lang.OutOfMemoryError异常。举个例子来说,有个emp表,存储员工的id姓名年龄等,当只有千百条时,取出结果集转成链表再写入csv文件自然没什么问题,但如果数据越来越多,还是结果集放链表里又来不及释放,总有内存不够的时候。
2019-10-25 14:43:05.518 ERROR 40016 --- [nio-8080-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: GC overhead limit exceeded] with root cause
java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.lang.Integer.toString(Unknown Source) ~[na:1.8.0_201]
at java.lang.String.valueOf(Unknown Source) ~[na:1.8.0_201]
at com.example.demo.CsvUtil.exportBigCsv(CsvUtil.java:78) ~[classes/:na]
at com.example.demo.SpringBootWeb1Application.downloadBigCsvFile(SpringBootWeb1Application.java:141) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_201]
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[na:1.8.0_201]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[na:1.8.0_201]
at java.lang.reflect.Method.invoke(Unknown Source) ~[na:1.8.0_201]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:892) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1039) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.24.jar:9.0.24]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
要解决这个问题,就得耐下心来,将整体读整体写改成分批读分批写(分批方案和分页方案类似,诸位可自行寻找合适自己DB的DB dialect),最后再下载。
示例页面:
<!DOCTYPE html>
<html lang="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<head>
<title>happy go lucky</title>
</head>
<body>
<h2>文件下载测试</h2>
<hr/>
<ol>
<li><a href="/dldLocalFile">下载本地文件dld.rar</a></li>
<li><a href="/dldGeneratedFile/100">下载生成百行CSV文件</a></li>
<li><a href="/dldGeneratedFile/100000000">下载生成亿行CSV文件</a></li>
<li><a href="/dldGeneratedFile2/10000000">下载生成千万行CSV文件 可能 出现java.lang.OutOfMemoryError: GC overhead limit exceeded</a></li>
<li><a href="/dldGeneratedFile3/10000000">下载生成千万行CSV文件 不会可能 出现java.lang.OutOfMemoryError: GC overhead limit exceeded</a></li>
</ol>
</body>
</html>
<script type="text/javascript">
<!--
// 脚本
//-->
</script>
控制类:
package com.example.demo;
import java.io.File;
import java.io.FileInputStream;
import java.net.URLDecoder;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@SpringBootApplication
public class WebFileDownloadApplication {
private static Logger logger = Logger.getLogger(WebFileDownloadApplication.class);
public static void main(String[] args) {
SpringApplication.run(WebFileDownloadApplication.class, args);
}
@RequestMapping("/")
public String index() {
logger.info("进入index页");
return "index.html";
}
@RequestMapping("/dldLocalFile")
public void downloadLocalFile(HttpServletResponse res, HttpServletRequest req) throws Exception {
logger.info("下载本地文件");
String localFilename = "dld.rar";
String localFilepath = getClass().getResource("/static/" + localFilename).getPath();
res.setContentType("multipart/form-data");
res.setCharacterEncoding("UTF-8");
res.setContentType("text/html");
String userAgent = req.getHeader("User-Agent");
if (userAgent.contains("MSIE") || userAgent.contains("Trident")) {
// IE Core
localFilename = java.net.URLEncoder.encode(localFilename, "UTF-8");
} else {
// Non-IE Core
localFilename = new String((localFilename).getBytes("UTF-8"), "ISO-8859-1");
}
res.setHeader("Content-Disposition", "attachment;fileName=" + localFilename);
localFilepath = URLDecoder.decode(localFilepath, "UTF-8");
FileInputStream instream = new FileInputStream(localFilepath);
ServletOutputStream outstream = res.getOutputStream();
int b = 0;
byte[] buffer = new byte[1024];
while ((b = instream.read(buffer)) != -1) {
outstream.write(buffer, 0, b);
}
instream.close();
if (outstream != null) {
outstream.flush();
outstream.close();
}
}
@RequestMapping("/dldGeneratedFile/{count}")
public void downloadGeneratedCsvFile(HttpServletResponse res, HttpServletRequest req,@PathVariable String count) throws Exception {
logger.info("Start downloadGeneratedCsvFile");
SimpleDateFormat dfs = new SimpleDateFormat("yyyyMMddHHmmss");
Date time = new Date();
String tStamp = dfs.format(time);
String localFilename = tStamp+".csv";
String path=req.getSession().getServletContext().getRealPath("/");
String localFilepath = path+localFilename;
logger.info("准备生成的本地路径文件名="+localFilepath);
res.setContentType("multipart/form-data");
res.setCharacterEncoding("UTF-8");
res.setContentType("text/html");
String userAgent = req.getHeader("User-Agent");
if (userAgent.contains("MSIE") || userAgent.contains("Trident")) {
// IE Core
localFilename = java.net.URLEncoder.encode(localFilename, "UTF-8");
} else {
// Non-IE Core
localFilename = new String((localFilename).getBytes("UTF-8"), "ISO-8859-1");
}
res.setHeader("Content-Disposition", "attachment;fileName=" + localFilename);
localFilepath = URLDecoder.decode(localFilepath, "UTF-8");
List<String> dataList=new ArrayList<String>();
for(int i=0;i<100;i++) {
dataList.add(String.valueOf(i));
}
File file=new File(localFilepath);
CsvUtil.generateCsv(file,Integer.parseInt(count));
logger.info("已经生成文件:"+localFilepath);
FileInputStream instream = new FileInputStream(localFilepath);
ServletOutputStream outstream = res.getOutputStream();
int b = 0;
byte[] buffer = new byte[1024];
while ((b = instream.read(buffer)) != -1) {
outstream.write(buffer, 0, b);
}
instream.close();
if (outstream != null) {
outstream.flush();
outstream.close();
boolean isDeleted=file.delete();
if(isDeleted) {
logger.info("已经删除文件:"+localFilepath);
}
}
}
@RequestMapping("/dldGeneratedFile2/{count}")
public void downloadGeneratedCsvFile2(HttpServletResponse res, HttpServletRequest req,@PathVariable String count) throws Exception {
logger.info("Start downloadGeneratedCsvFile2");
SimpleDateFormat dfs = new SimpleDateFormat("yyyyMMddHHmmss");
Date time = new Date();
String tStamp = dfs.format(time);
String localFilename = tStamp+".csv";
String path=req.getSession().getServletContext().getRealPath("/");
String localFilepath = path+localFilename;
logger.info("准备生成的本地路径文件名="+localFilepath);
res.setContentType("multipart/form-data");
res.setCharacterEncoding("UTF-8");
res.setContentType("text/html");
String userAgent = req.getHeader("User-Agent");
if (userAgent.contains("MSIE") || userAgent.contains("Trident")) {
// IE Core
localFilename = java.net.URLEncoder.encode(localFilename, "UTF-8");
} else {
// Non-IE Core
localFilename = new String((localFilename).getBytes("UTF-8"), "ISO-8859-1");
}
res.setHeader("Content-Disposition", "attachment;fileName=" + localFilename);
localFilepath = URLDecoder.decode(localFilepath, "UTF-8");
List<String> dataList=new ArrayList<String>();
for(int i=0;i<100;i++) {
dataList.add(String.valueOf(i));
}
File file=new File(localFilepath);
CsvUtil.generateCsv2(file,Integer.parseInt(count));
logger.info("已经生成文件:"+localFilepath);
FileInputStream instream = new FileInputStream(localFilepath);
ServletOutputStream outstream = res.getOutputStream();
int b = 0;
byte[] buffer = new byte[1024];
while ((b = instream.read(buffer)) != -1) {
outstream.write(buffer, 0, b);
}
instream.close();
if (outstream != null) {
outstream.flush();
outstream.close();
boolean isDeleted=file.delete();
if(isDeleted) {
logger.info("已经删除文件:"+localFilepath);
}
}
}
@RequestMapping("/dldGeneratedFile3/{count}")
public void downloadGeneratedCsvFile3(HttpServletResponse res, HttpServletRequest req,@PathVariable String count) throws Exception {
logger.info("Start downloadGeneratedCsvFile3");
SimpleDateFormat dfs = new SimpleDateFormat("yyyyMMddHHmmss");
Date time = new Date();
String tStamp = dfs.format(time);
String localFilename = tStamp+".csv";
String path=req.getSession().getServletContext().getRealPath("/");
String localFilepath = path+localFilename;
logger.info("准备生成的本地路径文件名="+localFilepath);
res.setContentType("multipart/form-data");
res.setCharacterEncoding("UTF-8");
res.setContentType("text/html");
String userAgent = req.getHeader("User-Agent");
if (userAgent.contains("MSIE") || userAgent.contains("Trident")) {
// IE Core
localFilename = java.net.URLEncoder.encode(localFilename, "UTF-8");
} else {
// Non-IE Core
localFilename = new String((localFilename).getBytes("UTF-8"), "ISO-8859-1");
}
res.setHeader("Content-Disposition", "attachment;fileName=" + localFilename);
localFilepath = URLDecoder.decode(localFilepath, "UTF-8");
List<String> dataList=new ArrayList<String>();
for(int i=0;i<100;i++) {
dataList.add(String.valueOf(i));
}
File file=new File(localFilepath);
CsvUtil.generateCsv3(file,Integer.parseInt(count));
logger.info("已经生成文件:"+localFilepath);
FileInputStream instream = new FileInputStream(localFilepath);
ServletOutputStream outstream = res.getOutputStream();
int b = 0;
byte[] buffer = new byte[1024];
while ((b = instream.read(buffer)) != -1) {
outstream.write(buffer, 0, b);
}
instream.close();
if (outstream != null) {
outstream.flush();
outstream.close();
boolean isDeleted=file.delete();
if(isDeleted) {
logger.info("已经删除文件:"+localFilepath);
}
}
}
}
数据生成类:
package com.example.demo;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
public class CsvUtil {
private static Logger logger = Logger.getLogger(CsvUtil.class);
// 这种方式不会内存溢出,因为创建的数组对象用于写完文件后在一个循环结束就释放了
public static boolean generateCsv(File file,int rowCount) {
try {
FileWriter fileWriter =new FileWriter(file, true);
for(int j=0;j<rowCount;j++) {
int lineNo=j;
String[] arr=new String[20];
for(int k=0;k<arr.length;k++) {
arr[k]=String.valueOf(k);
}
String info=String.valueOf(lineNo)+","+String.join(",", arr)+System.getProperty("line.separator");
fileWriter.write(info);
}
fileWriter.flush();
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
// 这种方式因为生成一个大list,容易造成内存驻留过多而溢出
public static boolean generateCsv2(File file,int rowCount) {
try {
// 先模拟访问数据库创建集合,当数据量大时会出现outofmemory异常
List<String[]> list=new ArrayList<String[]>();
for(int i=0;i<rowCount;i++) {
String[] arr=new String[20];
for(int k=0;k<arr.length;k++) {
arr[k]=String.valueOf(k);
}
list.add(arr);
}
// 再将集合写文件
FileWriter fileWriter =new FileWriter(file, true);
int index=0;
for(String[] arr:list) {
index++;
String info=String.valueOf(index)+","+String.join(",", arr)+System.getProperty("line.separator");
fileWriter.write(info);
}
fileWriter.flush();
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
// 这种方式改成分批写入文件方式,以规避内存溢出
public static boolean generateCsv3(File file,int rowCount) {
logger.info("generateCsv3");
try {
final int LinesOnceTime=10000;
int count=rowCount/LinesOnceTime;
for(int i=0;i<count;i++) {
// 再将集合写文件
FileWriter fileWriter =new FileWriter(file, true);
List<String[]> list=new ArrayList<String[]>();
// 这回list被限制在了LinesOnceTime件,这一步代表着从数据库分批取,oracle有townum支持,mysql有limit支持
for(int j=0;j<LinesOnceTime;j++) {
String[] arr=new String[20];
for(int k=0;k<arr.length;k++) {
arr[k]=String.valueOf(k);
}
list.add(arr);
}
int index=0;
for(String[] arr:list) {
index++;
int lineNo=i*LinesOnceTime+index;
String info=String.valueOf(lineNo)+","+String.join(",", arr)+System.getProperty("line.separator");
fileWriter.write(info);
}
fileWriter.flush();
fileWriter.close();
}
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
}
续篇请看:https://www.cnblogs.com/xiandedanteng/p/11747855.html
--END-- 2019年10月26日14:49:02
[SpringBoot/SpringMVC]从Webapp下载一个大文件出现java.lang.OutOfMemoryError: GC overhead limit exceeded怎么办?的更多相关文章
- 正确使用MySQL JDBC setFetchSize()方法解决JDBC处理大结果集 java.lang.OutOfMemoryError: Java heap space
昨天在项目中需要对日志的查询结果进行导出功能. 日志导出功能的实现是这样的,输入查询条件,然后对查询结果进行导出.由于日志数据量比较大.多的时候,有上亿条记录. 之前的解决方案都是多次查询,然后使用l ...
- 导入到eclipse里的工程挺大的,然后就一直报: An internal error occurred during: "Building workspace". GC overhead limit exceeded 这个错误。
解决方法: 原因是Eclipse默认配置内存太小需要更改Eclipse安装文件夹下的eclipse.ini文件. Eclipse.ini默认文件如下: 修改如下: -Xms1024m -Xmx2048 ...
- Facebook图片存储系统Haystack——存小文件,本质上是将多个小文件合并为一个大文件来降低io次数,meta data里存偏移量
转自:http://yanyiwu.com/work/2015/01/04/Haystack.html 一篇14页的论文Facebook-Haystack, 看完之后我的印象里就四句话: 因为[传统文 ...
- spring-framework-3.2.4.RELEASE 综合hibernate-release-4.3.5.Final一个错误Caused by: java.lang.NoClassDefFound
LZ一体化的今天spring-framework-3.2.4.RELEASE 综合hibernate-release-4.3.5.Final一个错误Caused by: java.lang.NoCla ...
- 精讲RestTemplate第6篇-文件上传下载与大文件流式下载
本文是精讲RestTemplate第6篇,前篇的blog访问地址如下: 精讲RestTemplate第1篇-在Spring或非Spring环境下如何使用 精讲RestTemplate第2篇-多种底层H ...
- mac下载百度云盘大文件及断点续传的方法
问题 作为资源共享平台, 百度云做的还是很出色的, "xxx site:pan.baidu.com"就可以找到很丰富的资源. 然而, 下载百度云上的文件就略蛋疼了. 早在12年的时 ...
- 推荐一个大文件查找工具---WizTree
DB备份.dump.电影等文件多了以后,经常遇到磁盘空间不够用的情况,日积月累本来清晰的目录结构找起来也很费劲,尤其是要查找删除无用的大文件.windows本身那差劲的搜索功能就不提了,从搜索引擎上查 ...
- 判断大文件是否上传成功(一个大文件上传到ftp,判断是否上传完成)
大文件上传ftp,不知道有没有上传完成,如果没有上传完成另一个程序去下载这个文件,导致下载不完整. 判断一个文件是否上传完成的方法: /** * 间隔一段时间去计算文件的长度来判断文件是否写入完成 * ...
- python如何打开一个大文件?
with open('a.csv','r') as f: for i in f: print(i) while True: a = f.readline() if not a: break f.rea ...
随机推荐
- 如何搭建一个基于nuxt.js的项目
介绍 nuxt.js(中文官方文档)是vue.js的一个通用型应用框架,有了之前搭建vue项目的过程之后,搭建一个nuxt项目就会十分简单. 搭建步骤 1.打开命令提示符,进入到相关文件夹下: 2.使 ...
- windows使用zip包安装mysql8.0.12
1.前言 在windows下有两种安装mysql的方式,一种是msi的方式,一种是使用zip包的安装方式.通常都是用msi的方式,毕竟不需要敲命令,只用图形界面就可以完成安装.zip包的安装方式也很简 ...
- Mycat详解及配置读写分离(Centos7)
目录 一.理论概述 二.环境 三.部署 一.理论概述 原理简述 参考文档 MyCAT主要是通过对SQL的拦截,然后经过一定规则的分片解析.路由分析.读写分离分析.缓存分析等,然后将SQL发给后端真实的 ...
- win10开机后将存在多个系统选择,改为直接进入系统无需选择
win10系统安装后,可能出现每次开机都要选择操作系统,比较麻烦,所以就来设置下如何直接进入系统,无须选择 1.我的电脑右键“属性”—“高级系统设置”—“系统属性” 2.设置“启动和故障恢复”如下 选 ...
- doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS.
在settings.py中增加 INSTALLED_APPS = [ ... 'django.contrib.sites', ] 问题就解决了.什么原因.——不知道.. 具体请看: https://s ...
- YOLO---多个版本的简单认识
YOLO---多个版本的简单认识 YOLOv3 有好几个经典版本了:一.YOLOv3 (Darknet)官网 @ https://github.com/pjreddie/darknet二.YOLOv3 ...
- PHP返回JSON数据及中文编码问题的解决方案
在处理app接口的时候 ,中文在经过json_encode之后 变成\ \格式 想在返回接口的时候 中文不被转换 解决办法 第一种解决办法 exit(json_encode($result,JSON ...
- Derby 数据库基本操作 命令
0. 命令行 登录/退出 登录 java org.apache.derby.tools.ij ..\dirs>java org.apache.derby.tools.ij ij 版本 10.3 ...
- 记录一下关于DQN的想法
下载了几份代码,就两份没有报错通过了 DQN玩FlappyBird https://github.com/yenchenlin/DeepLearningFlappyBird DQN玩Cartpole ...
- mysql查询表中最后一条记录
查询全部的记录: select * from test_limit ; 查第一条记录: select * from test_limit limit 1; ...