引用中间件导致的OOM问题调查
一次使用中间件引发的OOM调查
描述问题
在上一次上线完成后,线上的系统在运行几天后就会收到FullGC频繁的告警,而且每台服务都会告警,万能的重启可以解决问题,但不是根本办法,肯定是上一期的代码出现了内存泄漏,在预发环境上,由于操作次数较少,这个问题很难暴露。于是dump 线上的内存,用JProfiler对hprof 文件进行了分析
分析及解决过程
分析文件
使用jprofiler打开 dump文件后,在大对象分析中,发现有个http 请求的封装类有居然有130141个实例,着实把我吓了一跳。
这个实例在代码中的确是prototype的,但是在引用他的地方,使用spring的注入方式,不会存在依赖无法释放的问题。在AlphaDataRemoteData 类中目前只存在两个注入的变量
查看Jprofiler实例的引用关系:
猜想及验证
可以看到一个FieldValueProcessor 引用了AlphaDataRemoteData这个类,同时也引用了 多例类中注入的@Value;spring的变量注入在populateBean中就会完成,不会生成单独的类,处理注入的value,在看FieldValueProcessor类的包名,猜想应该是引用上次上线引入的中间件导致的问题,引入的中间件的功能是可以在线修改配置变量,不用重启服务就可以生效。该中间件支持spring原生的@Value注入。查看FieldValueProcessor的源码:
确实引用类bean,而最终的GCRoot中是单例的,他内部存储着一个ConcurrentHashMap来缓存这些需要监听的key和key所属的bean,这是一条强依赖的引用链,无法释放;这个类主要负责用户手动点击后,请求其他系统的,5天10万的数量级在数量上也是匹配的,也说明了为什么ConfigruatorManager为什么占用18M内存的原因
解决问题
解决问题的方法时切断引用关系。目前无法移除这个中间件,存在两种改法,
- 将AlphaDataRemoteData注入的property 移动到一个单例的类中,AlphaDataRemoteData 注入这个类;
- 将AlphaDataRemoteData修改为单例的,将其内部的成员变量改为外部传入的方式;
本着修改最小的原则,我们将注入的实例移动到一个单独的配置类中,并在AlphaDataRemoteData中注入了这个类,上到预发环境,解决问题;
总结
- 中间件这样实现的原因是为了在从properties解析存储到动态配置解析转换时减少开发,这里缺少一个case将多例的引用不做缓存,直接请求;
- 在springBoot 使用多例时注意,看是否有会被其他实例长期引用而无法释放,导致OOM;
- 在新功能上线之前,dump预发环境的内存,看是否存在某个类实例异常多的情况,使用jprofiler 分析一下引用关系,看是否出现了内存泄漏;