一、问题

2022.05.10,生产环境的一台lbb2b服务无响应,群报警保存dump文件时,内存已降到90%左右 

二、排查过程

主要通过两种方式定位问题

服务监控和日志(辅)

image.png

 通过服务监控能定位问题发生的大致时间,机器,cpu和内存状态(这部分截图没保留);再通过时间范围去筛选服务日志,可能会有所收获——当然,这次除了OOM时间段RT变慢外,其它响应没发现有什么问题。 

分析dump文件(主)

通过visualVM分析dump,打开后首先关注占用内存最大的对象

image.png

这些集合最后都指向对象:DashboardLearnStatictisInfoVo(以下称Vo)

和此Vo对象密切相关的有两个入口方法,下面是其中的一个:

@GwApi(key = "com.liepin.lbb2b.dashboard.v1.comp-active-statistic-info", name = "活跃人数")
@RequireCodes(EnumAuthCode.STATISTIC_ALL_QRY)
public List<DashboardLearnStatictisInfoVo> getCompDistributionStatisticInfo(Integer timeType, String deptIdStr) throws BizException;

review代码,分析方法逻辑,定位到这部分:
image.png

如果条件是“今年”或“去年”可能会产生大量对象……
但应该不会产生如此多的对象才对(下图的Vo对象有500w+)。

start-end的差值在后续逻辑中会产生对应数量的对象

image.png

再仔细分析,发现DateUtil方法调用有误
用一个main方法调试的结果如下:

image.png

问题的根本原因在于:方法的入参应该用天,而实际用了秒

错误用法一次会产生大概9221293多个对象,放到一个list里面;按一个对象48B算,大小会有400多M(这是算今年的,算去年的因为出负数了(int越界),会更多,1个G左右)
至于一个对象48B的原因,也是源自visualVM。

image.png

至此,一个困扰半年的问题基本定位出来了(错误代码的提交时间也基本能对上)。

三、补充

还有个疑问:为什么生产环境偶尔出现这个问题,而不是每次都出现?
原因在于,集合虽然堆积的对象多且大,但依然可以被gc回收;由于集合对象在老年代,因此只能被fullgc回收。

先看一张图,途中给出了生产环境的JVM运行参数:
image.png

我们得知:GC用的CMS,堆内存一共2G;新生代1g,其中s0s1 100m,edn 800m。
这样的s0、s1放不下400M的大集合会直接进入老年代。

频繁的fullgc会“stop the world”,如果此时再遇到频繁请求(之前怀疑的小铃铛都是帮凶),很容易出现oom(实际上,同事在预生产环境一人就复现了此问题)。

总结一下:一个工具类的错误使用,导致了这个问题。隐藏深,不易发现。
我们有没有必要对DateUtil这个工具类进行一番梳理?名称类似的时间日期工具类过多,方法杂乱,还不全……

最后,作为开发我们应该敬畏每一行代码。


青鱼
268 声望25 粉丝

山就在那里,每走一步就近一些


下一篇 »
选人组件