ThreadLocal内存泄漏
内存泄漏:对象不再使用,但是仍然驻留在内存中。
ThreadLocalMap中Entry的结构
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value; // ThreadLocal变量值
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap内部底层是Entry数组,Entry的key为ThreadLocal,value是线程的该ThreadLocal变量值。Entry内部类继承了WeakReference类,是弱引用。我们可以看到每个Entry同时包含了一个对key的弱引用和一个对value的强引用。
弱引用的特点是,如果这个对象只被弱引用关联(没有任何强引用关联),那么对象可以被回收。弱引用不会阻止GC。
正常情况下,当线程终止,会将线程的ThreadLocalMap值设为null。因为没有了强引用,保存在ThreadLocal中的value会被垃圾回收器回收。
强引用链
如果强引用一直在,垃圾回收器就不会被回收,因此可能导致内存泄漏。
如果采用线程池,则线程很长时间不会终止,那么key对应value就不会回收,因为有下面的调用链:
Thread -强引用-> ThreadLocalMap -强引用-> Entry(key为null)-强引用-> value
Thread和value存在这个强引用链路,导致value无法回收,从而导致OOM.
JDK中对此有做处理,在ThreadLocalMap的set、remove、rehas方法内部的resize操作中,有如下语句
if (k == null) {
// 过期Entry,清空value
e.value = null; // 利于垃圾回收
然而,如果ThreadLocal不被引用,或者不调用set、remove、rehash方法,那么调用链会一直存在,导致value的内存泄漏。
解决措施
使用完ThreadLocal后,调用remove方法,删除对应的Entry对象,避免内存泄漏。
// 此时使用完ThreadLocal,回收该ThreadLocal
UserInfoHolder.holder.remove();
线程池不适合使用ThreadLocal。
NPE问题
public class WrongWayNPE {
private static ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<>();
public static void main(String[] args) {
System.out.println(integerThreadLocal.get());
System.out.println(getThreadLocal());
}
static int getThreadLocal() {
return integerThreadLocal.get();
}
}
如代码中ThreadLocal的泛型类型是Integer类型,未初始化值直接调get()方法,得到的值是null。null无法拆箱装换成基本类型,引发空指针异常。
因此,我们了解NPE问题不是ThreadLocal的问题,而是开发人员代码问题。