内存飙高之内存泄露 周末上午, 群里反馈, 线上系统响应慢或没有响应.
查看系统JVM内存情况, 老年代内存一直在缓慢增长, GC无法回收. 粗暴手段, 机器重启.
机器重启之后, 老年代内存依然在缓慢增长. 当晚,让运维将堆内存Dump下来.
第二天上午, 再次让运维将堆内存Dump下来.
查看堆转储文件
如上图查看Histogram, 搜索关键字, 例如公司包名, 找到可疑对象
继续查看哪些对象引用该可疑对象
1.需要熟练使用MAT等分析工具2.内存持续增长不被释放, 一般与线程有关 可疑对象被MQ线程引用
以下是同一个MQ线程的ThreadLocal部分数据
MQ线程的ThreadLocal内部有大量的SQL对象数据.
问题原因: 分析业务, 该SQL对象被代码里一个自定义的MyBatis拦截器使用, 当业务代码在执行增删改操作时, 该拦截器会将增删改SQL语句封装成对象放进ThreadLocal中. 会在HTTP拦截器位置, 将ThreadLocal中的SQL对象取出来传到阿里云日志. 会在下一次HTTP请求时, 将ThreadLocal里的数据移除. 因此可以认为不存在内存泄露.
但是自定义的MyBatis拦截器是通用的逻辑, MQ线程在执行业务的过程中, 访问数据库生成的SQL语句也一样会被自定义的MyBatis拦截器拦截, 将SQL语句封装成对象放进ThreadLocal中. 但是MQ线程没有时机再将这些ThreadLocal中的数据移除, 就会造成内存泄露. 老年代内存一直在缓慢增长, 且无法被回收.
解决:
在MQ线程的consume方法里, 手动ThreadLocal#remove