51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

线上报了内存溢出异常,又不完全是内存溢出

# (一)前言 {#一-前言}

最近一直忙于对付即将上线的系统,期间也碰到了很多问题。最近印象比较深的是一个内存溢出的报错。测试告诉我最近某个功能总是没有效果,于是我就去线上看了一下错误日志,这不看不知道,一看吓一跳,满屏的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);  
}

1
2
3
4
5
6
7
8
9
10
11
12

我特意在1000条处理完成后清空了List,就不存在内存溢出的情况。

# (三)查看GC日志 {#三-查看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]

1

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接口配置}

依稀记得dubbo接口调用时设置了每次的调用大小,于是上nacos检查配置,果然dubbo接口设置了16M的大小,这一下就定位到问题了。每次从dubbo接口取1000条数据在某些数据量比较大的情况下超过了16M,返回了一个OutOfMemory Error。

# (五)解决方案 {#五-解决方案}

既然定位到了问题,解决方案也就简单了,首先根据实际情况调整dubbo接口的消费者和生产者限制大小,其次将每次调用1000次修改略微小一点。再者按照原来的设计1000条数据是不会超过16M的,于是检查了数据,发现有的数据单条就超过了2M,这类数据的使用价值不大,因此在产品上考虑是否过滤掉这些数据。最终上线验证没有再报同样的问题,算是解决了。

# (六)总结 {#六-总结}

虽然问题是解决了,但是还是走了一些歪路。碰到紧急问题时脑子不会像事后那么清晰,但是踩的坑越多,学到的也就越多。

赞(1)
未经允许不得转载:工具盒子 » 线上报了内存溢出异常,又不完全是内存溢出