jdk版本:1.8
第一次写博客,原因是为了公司的技术分享,写ThreadLocal相关的文章的原因是公司用的代码检测工具标记ThreadLocal对象存在内存泄露问题,加上之前对这个东东并不熟悉,于是看了一下源码找了一下资料,决定写一篇相关的博客作为分享。
* <p>Each thread holds an implicit reference to its copy of a thread-local
* variable as long as the thread is alive and the {@code ThreadLocal}
* instance is accessible; after a thread goes away, all of its copies of
* thread-local instances are subject to garbage collection (unless other
* references to these copies exist).
这是ThreadLocal类的注释,可以看出ThreadLocal类是一个给多个线程使用并提供独立的数据的一个工具。
//ThreadLocal.Class
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//ThreadLocal.Class
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
从上边两段代码可以看出,实际上存储数据的是一个叫做ThreadLocalMap的类,而这个类会赋值给当前线程的threadLocals这个属性,也就是说不同的线程即使持有同一个ThreadLocal对象,在使用set方法的时候,都会生成各自的ThreadLoaclMap,保证了数据互不影响。
而内存泄漏是怎么一回事呢?
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap的内部类Entry的key是ThreadLoacl对象的弱引用,弱引用在每次gc的时候都会被回收,但是只是key而已,Entry对象并不是弱引用,根据jvm的可达性分析算法,我们从上文可以看出来,当一个线程没有结束之前并且threadLocals属性是已经赋值ThreadLocalMap的时候,ThreadLocalMap对象则一直不会被回收,即使ThreadLoacl对象已经消亡。所以就会存在内存泄漏的问题。所以每一个线程使用ThreadLocal中的数据后要记得调用remove方法将引用断开,保证正确的gc不造成内存泄漏的可能。
我们的项目是在aop中声明了一个静态的ThreadLocal<Long>对象,切点进入之前会set一个时间戳进去,结束的时候拿出这个时间戳和当前时间做一个时间差的计算,了解到这个内存泄漏的风险之后,在切点结束之后会调用remove方法。
强引用,软引用,弱引用这些,可达性分析算法这些自行百度。。。ThreadLocalMap中的数据结构等源码和remove源码等以后可能会更新,建议读者先自行观看,拜拜。