ThreadLocal 内存泄漏问题详解
大约 2 分钟JavaJuc
ThreadLocal的内存泄漏问题
使用ThreadLocal的逻辑如下:
- 首先创建一个ThreadLocal
- 往ThreadLocal中存放数据
- 取出ThreadLocal中的数据,移除ThreadLocalMap中的元素
我们创建的ThreadLocal是static类型的,存储在一个UserContext内,这个单例对象由Spring管理,这样可以保证在存数据和取数据时,使用的是同一个threadlocal,不同的Thread有不同的ThreadLocalMap,但是key都是同一个对象
在Spring中,ThreadLocal不会被释放,但是如果Entry在使用后没有被手动删除,就会在这个线程运行的过程中出现内存泄漏,但是在线程结束后还是会被释放,所以内存泄漏和弱引用、强引用没有关系,和手动释放内存有关系
//ThreadLocal的get()方法
public T get() {
//先获取当前线程和线程的ThreadLocalMap
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
//然后再到这个ThreadLocalMap中使用自己作为key取值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}引用关系
ThreadLocal 的引用关系是:
[Thread] --> [ThreadLocalMap] --> [Entry] --> [ThreadLocal (key, 弱引用)]
--> [Value (强引用)]正确使用方式
为了避免内存泄漏,使用完 ThreadLocal 后必须调用 remove() 方法:
try {
threadLocal.set(value);
// 业务逻辑
} finally {
threadLocal.remove(); // 必须手动清理
}为什么会内存泄漏
线程池场景:线程池中的线程是复用的,不会被销毁,所以 ThreadLocalMap 也不会被清理
Entry 的 Value 是强引用:即使 ThreadLocal 对象被 GC 回收(因为是弱引用),Value 仍然存在于 ThreadLocalMap 中
Key 变成 null:当 ThreadLocal 被回收后,Entry 的 key 变成 null,但 value 还在,形成"僵尸"Entry
最佳实践
- 使用完毕后调用
remove() - 将 ThreadLocal 声明为
private static final - 在线程池场景中特别注意清理