ThreadLocal产生内存泄漏原理
- 强引用与弱引用:
ThreadLocal
内部使用ThreadLocalMap
来存储数据,ThreadLocalMap
的键是ThreadLocal
的弱引用。当ThreadLocal
对象在其他地方没有强引用指向它时,在垃圾回收时,这个ThreadLocal
对象就会被回收。
- 但是
ThreadLocalMap
中对应键值对的键虽然被回收了,但是值还是存在的,因为ThreadLocalMap
中的值是强引用。如果这个线程一直存活,那么这些值就一直无法被回收,从而导致内存泄漏。
- 生命周期不一致:
Thread
的生命周期通常比ThreadLocal
长。当ThreadLocal
不再被使用,其外部强引用被释放后,由于ThreadLocalMap
中对ThreadLocal
是弱引用,ThreadLocal
可能被回收,但ThreadLocalMap
中对应的值依然与Thread
绑定,只要Thread
不结束,这些值就会一直占用内存。
代码层面触发内存泄漏的情况
- 使用完
ThreadLocal
未清理:
public class ThreadLocalMemoryLeakExample {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
threadLocal.set("example value");
// 没有调用threadLocal.remove()方法
// 当threadLocal外部强引用被释放,就可能导致内存泄漏
}
}
- 线程复用场景:
- 在使用线程池等线程复用的场景下,如果
ThreadLocal
使用后没有清理。例如:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolThreadLocalLeak {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.submit(() -> {
threadLocal.set("value in thread");
// 这里没有调用threadLocal.remove()
});
executorService.submit(() -> {
// 线程复用,上一次线程执行遗留的数据可能导致内存泄漏风险
});
executorService.shutdown();
}
}
- 匿名
ThreadLocal
对象:
- 创建匿名
ThreadLocal
对象并使用,后续没有清理。例如:
public class AnonymousThreadLocalLeak {
public static void main(String[] args) {
new ThreadLocal<String>() {{
set("anonymous value");
}};
// 匿名ThreadLocal对象没有外部强引用,未清理可能导致内存泄漏
}
}