ThreadLocal 引起的内存泄露
前言
ThreadLocal被称为线程本地存储,也就是实现线程之间的数据隔离。在ThreadLocal中set(变量)作用域属于当前线程,其他线程无法访问到。ThreadLocal的应用场景在Mybatis中的SqlSession管理有体现,因为每个线程都有自己的Session一次数据库会话,这时候就可以使用ThreadLocal去实现线程间的Session隔离,但是ThreadLocal使用不当的话,也会存在内存泄露,垃圾对象无法被回收,下面主要将一下ThreadLocal的实现原理,为什么会造成内存泄露
1.ThreadLocal实现原理
看过一点ThreadLocal的源码都知道ThreadLocal的实现是通过ThreadLocalMap去实现的,确实,ThreadLocal是通过ThreadLocalMap实现线程隔离的,但是有几个点需要我们注意下,一个是ThreadLocalMap对象并不是维护在ThreadLocal中的,而是维护在Thread中的,是Thread的一个成员变量,类的定义在ThreadLocal中。希望大家不要搞混了。看个源码就明白了
可以看到ThreadLocalMap是维护在Thread中的,这样设计的好处是什么呢?我们知道从ThreadLocal中获取数据其实就是从ThreadLocalMap中获取,将ThreadLocalMap放在Thread中,那么ThreadLocalMap的生命周期就跟Thread一样,随着Thread生命周期执行完被销毁,那么ThreadLocalMap也可以被GC,进行回收。
我们都知道ThreadLocalMap是个key, value的结构,ThreadLocalMap里面维护了一个Entry数组的成员变量,而Entry是继承WeakReference弱引用的,而ThreadLocalMap的key是ThreadLocal,并且是被弱引用执行的,value就是我们set()传进来的对象。ThreadLocal的源码实现还是相对比较简单的,其中set()和get()操作都相类似
1. 先是获取到当前线程Thread对象
2. 然后从当前线程Thread对象获取到ThreadLocalMap对象
3. 拿到ThreadLocalMap对象后,通过ThreadLocal当前实例this的threadLocalHashCode获取到Entry对象(有点类似HashMap根据对象的hashCode拿到数组下标i)
4. 最后直接从Entry对象中获取到value就行
2.造成内存泄露
ThreadLocal引起的内存泄露跟它内存存储的数据结构有关系,首先看一段ThreadLocal的应用代码:
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set(new String("hello world"));
通过上一段代码,看一下ThreadLocal所现成的对象关系引用图:
我们可以从图中看到Entry对象中的key是弱引用于new ThreadLocal()对象的,而弱引用的特点就是无论内存是否充足,GC一旦发现此类对象引用就会被回收掉。这里JDK为什么需要设置成弱引用了?
我们先来假设下如果Entry对象的key是强引用于new ThreadLocal()对象的,假设左边的Thread线程一直没有执行完,而在程序中我们主动将threadLocal局部变量设置为threadLocal = null,也就是threadLocal与new ThreadLocal()对象之间强引用的线需要断开,此时new ThreadLocal()对象因为被Entry对象的key强引用着,所以堆内存中的new ThreadLocal()对象无法被回收,从而造成内存泄露。所以JDK将强引用换成了弱引用,这样就能保证threadLocal局部变量被设置成null时,能保证堆内存中的new ThreadLocal()对象能被回收。
虽然JDK将Entry中的key换成了弱引用于new ThreadLocal()对象,但是还是会存在内存泄露的线程,虽然能保证new ThreadLocal()对象被回收,但Entry对象的key就会是一个null值存在,而value值是个强引用new String()对象,就会导致new String()对象一直无法被访问到,无法被回收。这样也会造成内存泄露
为了避免ThreadLocal所造成的内存泄露,我们应该在每次使用完ThreadLocal.set()完后,主动的去调用remove()方法,去清除Entry对象中的数据
总结
ThreadLocal的实现原理还是比较简单的,所引起的内存泄露反倒是一个重点,前面大概讲了下ThreadLocal的原理,后面着重讲了ThreadLocal是怎样造成内存泄漏的,最好每次使用完ThreadLocal后try{}finally{}中去remove掉ThreadLocal中的Entry数据