文件的上传和下载是Web系统中的一个很普通的功能,实现的方式也有很多种,如利用 java.io 下面的各种IO类自己实现,或者利用 Commons IO 包中的 FileUtils 、 IOUtils 类中封装好的方法直接调用。

1
2
3
4
//此处省略N行代码
ServletOutputStream os = response.getOutputStream();
File zipFileDownLoad = //此处省略下载文件代码
IoUtil.write(os,true,FileUtil.readBytes(zipFileDownLoad));//读取文件下载

我们大部分情况均会采用以上的方式进行处理。
FileUtil.readBytes 这句代码会将文件之间一次性读取到内存中。如果配置内存不能满足文件大小,结果如下👇👇👇

1
java.lang.OutOfMemoryError: Java heap space //这个很熟悉吧

通常我们解决此问题方式均是

1
set JAVA_OPTS=%JAVA_OPTS% -server -Xms800m -Xmx800m -XX:MaxNewSize=256m -XX:MaxPermSize=256m

但即使按照把上面的参数配置都扩大一倍,在下载更大的文件时还是会遇到 java.lang.OutOfMemoryError: Java heap space 这个错误,上面的解决方法治标不治本。分析下异常堆栈可以发现问题产生的根源在于 FileUtil.readBytes这行代码,FileUtil.readBytes 会把文件一次性读入内存中,要下载的文件越大,需要占用的内存也越大,当文件的大小超过JVM和Tomcat的内存配置时,OutOfMemoryError 这个问题就会不可避免的发生。

搞清楚问题根源之后,那么我们如何处理此类问题。利用普通的文件输出流按字节分段写入文件,把占用的内存固定在一个指定的范围内,从根本上避免内存占用过高的问题。

1
2
3
4
5
6
7
8
InputStream is = new FileInputStream(fileDownLoad);//需要下载文件,读取固定的字节数,写入os中
int read;
byte[] bytes = new byte[2048];
while((read = is.read(bytes))!=-1){//按字节逐个写入,避免内存占用过高
os.write(bytes, 0, read);
}
os.flush();
is.close();//记得关闭流