一次threadLocal引发的血案

周二早上正在带薪拉S,群里突然弹出一个消息说一个统计报表数据无法加载了。

作为多年见过世面的老程序员,我当然不能轻而易举放弃等了一刻钟的坑位,于是@了一下鸡哥,麻烦鸡哥跟进一下。

过了半个小时,我回到工位,看见鸡哥愁眉苦脸,我这时候适时候的用湿手撸了撸头发说,94年的小年轻,不小了。重启一下服务吧。

果然好了。

享受着鸡哥崇拜的眼神。但是善于思考的我并没有停止思考,到底是什么原因呢?


项目背景:

该报表群会每隔两个小时从数仓中拉取原始数据,加载到redis/以及java对象中。原始数据比较大。可能会有2G左右。

看到项目日志,果然有OOM。


于是我直接dump了一份预发上的日志,用eclipse Anylisis分析一下。扫描了三个比较大的对象,发现前两个对象MRData和overviewData都是之前预存到java缓存中的原始数据。应该是预期之内的。但是第三个对象,HashMap,大概有20多万条,并且里面的数据和MRData和overviewData对象中保持的hashmap几乎是一致。




为什么会duplicate这么多hashmap,是如何引发的呢?

考虑到日志中的OOM,均发生在quatz同步数据的时候,我想,会不会是同步数据的时候,旧的那份数据没有清除呢?

于是我拉上鸡哥一起走读代码:

发现在计算逻辑内,有大量threadLocal变量:


而这些threadLocal都指向了之前缓存的那些java对象。

果然,这应该就是罪魁祸首。

虽然我们每次做原始数据刷新的时候,都把缓存对象指向了从数仓里读取的新的对象。但是之前那些threadLocal对象,是由tomcat线程池生成的,如果这些线程没有释放的话,那么依然保持着对旧的对象的强引用。因此,就会出现:即使刷新job中对缓存对象的reference改变了,但这些旧的缓存对象依然没有释放的情况。


所以,我们要做的,就是每次去取java缓存对象的时候,用完之后将threadLocal中的对象直接从线程中的map里remove掉。



修改之后发线上,基本内存保持稳定。



这次引发我几个思考

第一,90%的OOM问题都是由代码不规范引发,因此要从业务上排查OOM。

第二,对业务逻辑的理解在OOM排查很重要,比如鸡哥很了解业务,一眼就能看出问题。

第三,高并发情况下threadLocal变量及时释放,做到有借有还,再借不难。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。