ThreadLocal - 内存泄漏
需求
看到有人说:Threadlocal的错误使用,会导致内存溢出,所以来分析一下源码。
结论
简而言之,就是通过 ThreadLocal 添加的 value 与 Thread 是强引用关系,在 Thread 无法正常销毁的场景下,有内存泄漏的隐患,例如:线程池。
解决方案:代码执行完成之后,要注意调用 remove() 函数清除数据。
反例
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
class Test{
/**
* 代码功能:通过 ThreadLocal 设值,使用结束之后,未清除数据,后续所有的循环都能读到这个值。
*
* 代码分析:正常的业务中,运行结束之后,线程被回收,处于闲置状态,但是这个值依然保存在内存中,这显然不合适。
*
* @param args -
*/
public static void main(String[] args) {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(1);
executor.setMaxPoolSize(1);
executor.setThreadNamePrefix("test-");
executor.initialize();
ThreadLocal<String> local = new ThreadLocal<>();
int cnt = 100;
while (cnt-- > 0) {
int finalCnt = cnt;
executor.execute(() -> {
System.out.println(finalCnt);
if (local.get() != null) {
System.out.println("已经存在值:" + local.get());
} else {
local.set("cnt: " + finalCnt);
}
});
}
}
}
分析
只需要关注 ThreadLocal 的 set() 函数即可,
观察 set() 函数,就能发现这样一条引用链:Thread > ThreadLocalMap > Entry > value。
线程是特殊的对象,它是可回收的,还会出现永久锁死的情况,因此需要关注内存泄漏的问题。
其它注意点:
- Entry 对象虽然继承自 WeakReference(弱引用),但是只与 key 值是弱引用关系,与 value 值是强引用关系;
- 引用链不包含 ThreadLocal 本身,ThreadLocal 只是一个用于存取值的工具;
- 因为引用链不包含 ThreadLocal,因此将 ThreadLocal 声明成 static 变量,也是可以的;
/**
* Thread 关键源码
*/
public class Thread implements Runnable {
// 每一个线程内部都有一个 Map,很明显存在隐患:如果未清理,数据会越堆越多。
ThreadLocal.ThreadLocalMap threadLocals = null;
}
/**
* ThreadLocal 关键源码
*/
public class ThreadLocal<T> {
// 设值函数,过程不涉及成员变量,因此 ThreadLocal 本身没有泄漏的风险
public void set(T value) {
// Thread 与 ThreadLocalMap 之间是强引用关系
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
// ThreadLocalMap 关键代码
static class ThreadLocalMap {
// 拥有 Entry 的引用
private Entry[] table;
static class Entry extends WeakReference<ThreadLocal<?>> {
// 注意:这里是强引用关系
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
}
疯狂的妞妞 :每一天,做什么都好,不要什么都不做!