ThreadLocal内存泄露问题
关系示意
问题分析
我们知道ThreadLocal中的值实际是和线程绑定的,从上面的关系示意中我们可以看到我们放在set到ThreadLocal中的值实际被放到了ThreadLocalMap的Entry中作为key,这个key从源码中我们可以看到是一个弱引用,当我们使用的ThreadLocal引用使用结束说,ThreadLocal对象 会被回收,但这并不意味着整个Entry会被回收,因为Entry实际还被Thread所引用,在实际应用中Thread通常被线程池管理重复使用除非重启几乎不会销毁。Entry的key由于是弱引用,最终会在下次GC被回收,但value而不然,此时就发生了所谓的**内存泄露
**
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
问题与思考
为什么Entry的key要使用弱引用?
这其实就是利用弱引用的特点,它始终会下一次GC被回收,在thread是线程池可复用的情况下,entry始终被强引用,如果key也使用强引用将导致它除非被手动回收,否则将永远驻留在内存,而使用弱引用则可以利用GC将其自动回收
Entry中的key被弱引用,那它岂不是一定会在下次GC被回收,那我们怎么通过key找到我们保存的object呢?
注意,key不仅仅仅被Entry弱引用,还被我们自己写的使用ThreadLocal所强引用,所以在我们写ThreadLocal被销毁前,Entry中的key并不会被GC回收!
如果避免这个问题
在使用完ThreadLocal手动调用的remove(ps:就算没有内存泄露的问题,也应用这样做,避免线程复用时获取到错误的数据)
ps:实际ThreadLocal自己也会清理key为null的value在调用get时获取不到(但实际情况下除非发生Hash冲突,几乎很少走到这里)
public T get() {
...
ThreadLocalMap.Entry e = map.getEntry(this);
...
}
private Entry getEntry(ThreadLocal<?> key) {
// 通过hash确定位置
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
...
if (k == null)
// 如果key为空,会把对应的value也设置为null,使其在下次GC被回收
expungeStaleEntry(i);
...
}