ThreadLocal原理
- ThreadLocal是Java中所提供的线程本地存储机制,可以利⽤该机制将数据缓存在某个线程内部,成为线程内的局部变量
- 该线程可以在任意时刻、任意⽅法中获取缓存的数据
- 这样每个线程都自己管理自己的局部变量,别的线程操作的数据不会产生影响,互不影响
- 一般使用ThreadLocal,官方建议定义为private static
- ThreadLocal底层是通过ThreadLocalMap来实现的,每个Thread对象(注意不是ThreadLocal对 象)中都存在⼀个ThreadLocalMap,Map的key为ThreadLocal对象,Map的value为需要缓存的 值
public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
- 其中2为弱引用
ThreadLocal内存泄漏问题
- 内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果
- 不当使用ThreadLocal可能会导致内存泄漏
- 因为当ThreadLocal对象使⽤完之后,应该要 把设置的key,value,也就是Entry对象进⾏回收
- 如果线程长期没有结束(如线程池中的核心线程),线程对象是通过 强引⽤指向ThreadLocalMap,ThreadLocalMap也是通过强引⽤指向Entry对象,线程不被回收, Entry对象也就不会被回收,从⽽出现内存泄漏
- 解决办法:在使⽤了ThreadLocal对象之后,手动调⽤ThreadLocal的remove⽅法,手动清除Entry对象
ThreadLocal内存泄漏模拟
过程剖析
- 在网上查阅了大量的文章,发现将ThreadLocal原理的文章一抓一大把,而且内容千篇一律,但却鲜有将内存泄漏现象模拟出来的。
- 因此,本文的重点是对ThreadLocal内存泄漏现象的模拟,但原理是模拟的前提,因此还是提前介绍了原理。
- 定义一个类继承Thread,并重写run方法,run方法里编写模拟逻辑:为threadlocal对象赋值 —》将threadlocal对象置空—》调用System.gc()手动进行垃圾回收—》观察前后 Thread对象中 ThreadLocalMap的变化
public class TestForThreadLocal {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
TestThread test = new TestThread();
test.start();
}
}
class TestThread extends Thread {
@Override
public void run() {
System.out.println("start...");
TestForThreadLocal test = new TestForThreadLocal();
test.threadLocal.set("test");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
test = null;
System.gc();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end....");
}
}
- 在执行 test = null前
- 可以发现,线程的threadLocals的table中,10号引用指向了 referent为ThreadLocal@492的对象,value为“test”
- 调用System.gc()进行垃圾回收后
- 可以发现,此时10号的referent已经变为null,但value没有变化,仍然为“test”
- 这就已经出现了内存泄漏现象:value = “test”这个值一直存在,但却没有引用能够访问到它了。
深入源码
- 那么这个referent对象是啥,为啥会因为gc而变为null?
/**
可以看到,map对象 entry继承了弱引用类
在构造方法中,调用了super(k)将 key对象传给了父类
**/
public class ThreadLocal<T> {
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
}
/**
在其父类中,我们找到了想要找的 referent
**/
public class WeakReference<T> extends Reference<T> {
public WeakReference(T referent) {
super(referent);
}
}
/**
通过将referent加入 弱引用队列,保证下次gc时,referent能够被回收,因此便出现了 gc后 该值变为null的情况
**/
public abstract class Reference<T> {
Reference(T referent) {
this(referent, null);
}
}