如何理解ThreadLocal引发的内存泄露
这篇文章主要介绍"如何理解ThreadLocal引发的内存泄露",在日常操作中,相信很多人在如何理解ThreadLocal引发的内存泄露问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"如何理解ThreadLocal引发的内存泄露"的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
需求背景
一个to C的个人账户系统,数据量大概有2000多W;某天产品突然来说要做一个to B 商户账户系统,马上一个星期左右后的大促活动就要用,产品跟大老板牛逼已经吹出去了,说我们的系统已经具备了这个能力,能够立马无缝支持。现在火急火燎的来找我们,问是不是直接把我们这套to C的账户系统的能力提供出去就可以了。
需求分析
首先在这么短的时间内要开发测试上线,新做一个to B的账户系统肯定是不现实的,咱们程序员能力再强也不能流水线生产系统啊。只能依赖当前账户系统的能力先去支撑这个业务。这两套账户体系底层的核心领域模型可以抽象一致,都包括记账凭证,记账主体,记账流水。不同的地方在于不同的业务使用场景下进出帐的规则不一样,所以规则层的领域模型需要定制化的配置开发。同时目前系统已经有2000多W账户,日增加流水也是10W级别的,本身数据量已经很大,而且业务上两套数据会相互影响,需要在业务上做垂直分表,将两套数据隔离。
实施过程
领域模型和数据存储方案定下来后,马上就开始实施了,当时为了更优雅的实现,对目前to C的业务影响最小,我们特意在interface入口处统一拦截后在当前工作线程的threadlocal加上一个判断标记,用来识别是to C的业务请求还是to B的业务请求,然后业务规则层通过不同的filter进行过滤,最后在数据库jdbc层通过不同的标记对不同的表做相应的DML处理。方案实施的很快,两三天就开发完提测,可在测试过程中发现了两个问题,第一个问题是数据有时候会无缘无故的错乱,本来是写到to B表结构的数据会写到了to C表结构里。而且压测时发现内存监控曲线在慢慢的增长,发生了内存泄露。
问题分析
仔细分析源代码后发现问题出现在threadlocal。每个线程中都有一个ThreadLocalMap数据结构,当执行set方法时,其值是保存在当前线程的threadLocals变量中,当执行get方法中,是从当前线程的threadLocals变量获取。所以在线程1中set的值,对线程2来说是摸不到的,而且在线程2中重新set的话,也不会影响到线程1中的值,保证了线程之间不会相互干扰
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value);}public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue();}ThreadLocalMap getMap(Thread t) { return t.threadLocals;}//Entry为ThreadLocalMap静态内部类,对ThreadLocal的若引用//同时让ThreadLocal和储值形成key-value的关系static class Entry extends WeakReference> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal> k, Object v) { super(k); value = v; }}
内存泄露分析
通过代码可以知道,当一个线程调用ThreadLocal的set方法设置变量时候,当前线程的ThreadLocalMap里面就会存放一个Entry对象,这个记录的key为ThreadLocal的引用,value则为设置的值。如果当前线程一直存在而没有调用ThreadLocal的remove方法,并且这时候其它地方还是有对ThreadLocal的引用,则当前线程的ThreadLocalMap变量里面会存在ThreadLocal变量的引用和value对象的引用是不会被释放的,这就会造成内存泄露的。但是考虑如果这个ThreadLocal变量没有了其他强依赖,而当前线程还存在的情况下,由于线程的ThreadLocalMap里面的key是弱依赖,则当前线程的ThreadLocalMap里面的ThreadLocal变量的弱引用会被在gc的时候回收,但是对应value还是会造成内存泄露。
数据写错分析
java进程是通过外部web容器如tomcat启动的,web容器启动时会启动一个线程池,创建一些核心初始线程,这些线程处理完一个任务后会继续处理另外一个任务。这个时候假如核心线程刚处理完一个to B的任务,处理完后线程没有消失继续处理一个to C的任务,这个时候threadLocal里的变量标记还是直接的标记,就会导致在jdbc层继续被路由到to B的表结构里,导致数据错误。
修复处理
了解清楚threadLoca的存储结构后,在interface入口处做一个AOP切片,通过环绕通知,在方法正式处理前在当前工作线程的threadlocal set一个判断标记,方法处理完成后手动remve掉这个标记,保证每次处理后 内存正确被释放。
到此,关于"如何理解ThreadLocal引发的内存泄露"的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!