线上报了内存溢出异常,又不完全是内存溢出
作者:鱼仔
博客首页: https://codeease.top
公众号:Java鱼仔
# (一)前言
最近一直忙于对付即将上线的系统,期间也碰到了很多问题。最近印象比较深的是一个内存溢出的报错。测试告诉我最近某个功能总是没有效果,于是我就去线上看了一下错误日志,这不看不知道,一看吓一跳,满屏的OutOfMemoryError ,出于隐私保护,这里只展示其中的一点异常信息:
# (二)思考场景
一般查问题首先是看日志,然后是思考场景,为什么在这个场景下会发生内存溢出的异常。于是我就思考了一下这段代码的逻辑,这里是一个异步线程的数据提取功能:通过dubbo接口,每次调用1000条数据,再对这些数据做一些处理后落入库,数据的总量在几万至几十万不等。
这里如果会出现内存溢出,唯一有可能的是每次调用的1000条数据在数据处理后没有清空,导致几十万数据都加入进内存中,最后内存溢出。于是去检查了这部分的代码:
List<EmrTreatment> resultList = new ArrayList<>();
//如果数据还有,则不跳出循环
while (CollectionUtils.isNotEmpty(data.getRecords())) {
resultList.addAll(dataPage.getRecords());
//业务处理
//.......
//清空集合防止内存溢出
resultList.clear();
// 通过dubbo接口请求接下来的1000条
//... ...
data=searchDataByScroll(dataRequest);
}
2
3
4
5
6
7
8
9
10
11
12
我特意在1000条处理完成后清空了List,就不存在内存溢出的情况。
# (三)查看GC日志
因为是堆内存溢出,于是立刻想到了去看看GC日志,但是一点内存溢出的意思也没有,顺便重温一下GC日志的内容表示的含义:
以其中的单条为例:
GC (Allocation Failure) 2021-10-29T16:37:45.177+0900: 2686.339 [ParNew: 283195K->3579K(314560K), 0.0256691 secs] 396015K->116915K(1013632K), 0.0258253 secs] [Times: user=0.03 sys=0.02, real=0.03 secs]
GC: 表明进行了一次垃圾回收,属于MinorGC
Allocation Failure:GC发生原因是因为年轻代空间不足
ParNew:本次GC年轻代使用的是ParNew垃圾收集器
283195K->3579K(314560K):GC前年轻代使用量->GC后年轻代使用量(年轻代总容量)
396015K->116915K(1013632K):堆区垃圾回收前使用量->堆区垃圾回收后使用量(堆大小)
[Times: user=0.04 sys=0.00, real=0.01 secs]:
user:垃圾收集线程消耗的所有CPU时间
sys:系统等待时间
real:应用暂停总时间(STW)
既然GC日志中没有堆内存溢出的信息,说明不是我们应用的内存溢出,又仔细看了一下报错信息,有很明显的错误指向dubbo,说明之前的路走歪了。
# (四)检查dubbo接口配置
依稀记得dubbo接口调用时设置了每次的调用大小,于是上nacos检查配置,果然dubbo接口设置了16M的大小,这一下就定位到问题了。每次从dubbo接口取1000条数据在某些数据量比较大的情况下超过了16M,返回了一个OutOfMemory Error。
# (五)解决方案
既然定位到了问题,解决方案也就简单了,首先根据实际情况调整dubbo接口的消费者和生产者限制大小,其次将每次调用1000次修改略微小一点。再者按照原来的设计1000条数据是不会超过16M的,于是检查了数据,发现有的数据单条就超过了2M,这类数据的使用价值不大,因此在产品上考虑是否过滤掉这些数据。最终上线验证没有再报同样的问题,算是解决了。
# (六)总结
虽然问题是解决了,但是还是走了一些歪路。碰到紧急问题时脑子不会像事后那么清晰,但是踩的坑越多,学到的也就越多。