记一次OOM总结
前言:项目将要上线之际,后台管理交给乙方添加数据+上传资料等等,前台正微调中,在快要下班之际,乙方突然反应传一个300MB的视频没传上去,报错了(这儿就不要吐槽为啥用web传大视频了,已经被吐槽过了)。然后上服务器查看日志,错误信息是OutOfMemoryError :Java heap space。 项目是使用Spring boot搭建,文件上传到服务器,服务器再上传到七牛云。
为了让服务先能正常运行,给了参数-Xmx1g -Xms1g -Xmn300M。结果传个300M的还是因为OOM挂掉了。这儿就有点懵逼了,然后重新运行给了参数-Xmx2g -Xms2g -Xmn500M。这次总算没有挂掉。然后自己试了一个440M的没有问题,但过了十几分钟突然timeout。百度了下,改了server的connection到30min,终于可以正常运行。但事情还没有结束,带电脑回家分析产生OOM的原因。
回到家,首先在最开始参数的基础上加了+XX:+PrintGCDetails参数,查看下GC回收情况。
访问正常网页GC日志显示如下:
ps:这儿使用System.gc()强制gc。不建议在正式环境中使用这个方法。甚至要使用参数-XX:+DisableExplicitGC禁止显示调用gc。且这儿的参数被改为了-Xmn250M。
可以看到最终占用内存为41390K,大概40M左右。
然后再来上传400M的文件看下gc日志:
可以看到经过三次新生代gc都担保分配失败,从而进行了一次老年代gc以及新生代gc,同时回收的同时伴随着STW(Stop-the-World)现象,gc过后新生代使用为0K, 老年代还剩250M左右。
而此时上传文件方法:
@Override
public String upload(InputStream inputStream, String path) {
try {
byte[] data = IOUtils.toByteArray(inputStream);
return this.upload(data, path);
} catch (IOException e) {
throw new RRException("上传文件失败", e);
}
}
由于这儿使用了别人写的上传工具类(ps:不是七牛的SDK),将整个文件加载到内存。 440M的文件太大,jvm本着大对象直接进入老年代的原则将其放入老年代,而此时老年代只剩下不足300M的内存,于是就OOM了。
解决方法:
上传时判断文件大小,小于100M的使用原方法上传,大于100M采用七牛提供的分片上传,限制最大上传大小为500M。添加代码如下:
@Override
public String upload(File file, String path) {
if(file == null || file.isDirectory()) {
throw new RRException("上传文件为空!");
}
try {
if(file.getTotalSpace() > MessageUtils.MAX_FORM_FILE_SIZE) {
Response response = uploadManager.put(file, path, token, null, null, false);
if(response.isOK()) {
return config.getQiniuDomain() + "/" + path;
} else {
throw new RuntimeException("上传七牛云出错,请检测配置!" + response.toString());
}
} else {
return upload(new FileInputStream(file), path);
}
} catch (Exception e) {
e.printStackTrace();
throw new RRException("上传失败" + e);
}
}
采用分片上传速度也变快了,建议采用分片上传。总结:
1、出现OOM时常常是自己写的代码出错或逻辑有漏洞,书写相关逻辑时考虑得不全面,可能在不经意的地方产生大对象占用大量空间导致OOM,这时候要仔细检查相关代码。当然也有可能是真的内存不够用了。
2、一定要对用户上传的文件大小作出限制,不然多大的内存都会OOM,可以在tomcat设置,也可以在nginx中设置,如果项目使用spring boot,可以直接在spring boot的配置文件中设置。