一、问题
2022.05.10,生产环境的一台lbb2b服务无响应,群报警保存dump文件时,内存已降到90%左右
二、排查过程
主要通过两种方式定位问题
服务监控和日志(辅)
通过服务监控能定位问题发生的大致时间,机器,cpu和内存状态(这部分截图没保留);再通过时间范围去筛选服务日志,可能会有所收获——当然,这次除了OOM时间段RT变慢外,其它响应没发现有什么问题。
分析dump文件(主)
通过visualVM分析dump,打开后首先关注占用内存最大的对象
这些集合最后都指向对象: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代码,分析方法逻辑,定位到这部分:
如果条件是“今年”或“去年”可能会产生大量对象……
但应该不会产生如此多的对象才对(下图的Vo对象有500w+)。
start-end的差值在后续逻辑中会产生对应数量的对象
再仔细分析,发现DateUtil方法调用有误
用一个main方法调试的结果如下:
问题的根本原因在于:方法的入参应该用天,而实际用了秒
错误用法一次会产生大概9221293多个对象,放到一个list里面;按一个对象48B算,大小会有400多M(这是算今年的,算去年的因为出负数了(int越界),会更多,1个G左右)
至于一个对象48B的原因,也是源自visualVM。
至此,一个困扰半年的问题基本定位出来了(错误代码的提交时间也基本能对上)。
三、补充
还有个疑问:为什么生产环境偶尔出现这个问题,而不是每次都出现?
原因在于,集合虽然堆积的对象多且大,但依然可以被gc回收;由于集合对象在老年代,因此只能被fullgc回收。
先看一张图,途中给出了生产环境的JVM运行参数:
我们得知:GC用的CMS,堆内存一共2G;新生代1g,其中s0s1 100m,edn 800m。
这样的s0、s1放不下400M的大集合会直接进入老年代。
频繁的fullgc会“stop the world”,如果此时再遇到频繁请求(之前怀疑的小铃铛都是帮凶),很容易出现oom(实际上,同事在预生产环境一人就复现了此问题)。
总结一下:一个工具类的错误使用,导致了这个问题。隐藏深,不易发现。
我们有没有必要对DateUtil这个工具类进行一番梳理?名称类似的时间日期工具类过多,方法杂乱,还不全……
最后,作为开发我们应该敬畏每一行代码。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。