根据官网给出的定义:This class provides thread-local variables
,换句话说,threadLocal存储当前thread的局部变量
,该局部变量是线程私有的,Thread内部维护的属性代码如下:
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
ThreadLocal
维护的静态内部类ThreacLocalMap
,而ThreacLocalMap
维护的是静态内部类Entry类,Entry继承WeakReference
,如代码所示:
/**
* ThreadLocalMap is a customized hash map suitable only for
* maintaining thread local values. No operations are exported
* outside of the ThreadLocal class. The class is package private to
* allow declaration of fields in class Thread. To help deal with
* very large and long-lived usages, the hash table entries use
* WeakReferences for keys. However, since reference queues are not
* used, stale entries are guaranteed to be removed only when
* the table starts running out of space.
*/
static class ThreadLocalMap {
/**
* 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;
}
}
......
}
因为entry是弱引用,这里容易出现内存泄露。
了解内存泄露之前,我们需要了解Java引用类型 - 弱引用。
如果发生gc操作时,弱引用对象就会被回收,不管引用对象是否置为null。
引用对象不为null:
@Test
public void testReference() {
CbcCompany company = new CbcCompany();
company.setName("test company");
WeakReference<CbcCompany> weakReference = new WeakReference(company);
int i = 0;
boolean flag = true;
while (flag) {
i++;
if (weakReference.get() != null) {
System.out.println("cbcCompany is alive for " + i + " loops - " + weakReference.get());
} else {
System.out.println("cbcCompany has been collected.");
flag = false;
}
}
}
输出结果为:
@Test
public void testReference() {
CbcCompany company = new CbcCompany();
company.setName("test company");
WeakReference<CbcCompany> weakReference = new WeakReference(company);
company = null;
int i = 0;
boolean flag = true;
System.gc();
System.out.println("cbcCompany is alive for " + i + " loops - " + weakReference.get());
}
我们定义一个threadLocal对象,通过set方法注入值,此后threadLocal对象置位null,在垃圾回收时被回收。
Entry的key值为null,value值不为空,对象便被ThreadLocalMap引用着。
如果当前线程的生命周期比较长,那么外部的ThreadLocalMap对象也一直存在,因而这就形成一个强引用链:thread -> threadLocalMap -> entry ->null
。
因此,在使用完毕后,及时调用thread.remove()方法回收对象。
既然弱引用极容易被Gc回收,从而造成内存泄漏,那么,ThreadLocal为什么还要使用弱引用呢?
在线上环境,jvm默认给每个工作线程分配的内存大小为1mb,假如这里使用的是强引用,当线程执行完还没有被Gc回收,当前map也没有被回收,entry所维护的threadLocal的引用对象自然不会被回收,那么,这就会造成内存碎片。本来就不大的内存,加上碎片的产生,容易造成栈异常。除非线程结束,线程被回收了,map也跟着回收。
但是,如果使用线程池的话,当前核心线程永远都不会被回收,除非调用线程池的shutdown方法,线程才会被回收。但使用线程池就是为了以更少的线程来处理更多的任务,所以很少去调用线程池的shutdown方法。
但是,弱引用也会产生内存泄漏,假如threadlocal=null,gc回收threadlocallocal
,但map里面的value却没有被回收。而这块value永远不会被访问到了. 所以存在着内存泄露。但是,Java为了最小化减少内存泄露的可能性和影响,在ThreadLocal的get,set的时候都会清除线程Map里所有key为null的value。
当线程的某个localThread使用完了,马上调用threadlocal的remove方法,那就啥事没有!
其实,只要这个线程对象及时被gc回收,这个内存泄露问题影响不大。但在threadLocal设为null到线程结束,中间这段时间不会被回收的,就发生了我们认为的内存泄露。
最要命的是线程对象一直不被回收的情况,就发生了真正意义上的内存泄露。比如使用线程池的时候,线程结束是不会销毁的,会再次使用的。就可能出现内存泄露。