ThreadLocal内存泄漏问题。
ThreadLocal线程局部变量,保证每个线程都有自己独立的副本,避免线程之间的变量共享。下面我们以Page对象为例,分析一下ThreadLocal内存的结构,以及可能发生的内存泄漏问题。
代码示例:
public class RandomTest {
public static void main(String[] args) {
ThreadLocal<Page> pageLocal = new ThreadLocal<Page>();
Page page = new Page();
pageLocal.set(page);
}
}
class Page{
private int num;
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}
内存中的结构。
每一个Thread对象内部都有一个ThreadLocalMap对象,内部的key为ThreadLocal。为弱引用。也是容易引发内存泄漏的问题关键。Value为放入的对象,本章案例为Page对象。当ThreadLocalRef为空时。ThreadLocal对象就只包含弱引用。当发生GC时就会被回收掉。ThrealLocal在调用get和set方法时都会调用cleanSomeSlots方法将key为null的清除掉。或者可以将使用完的调用ThreadLocal.remove方法去除,避免内存泄漏。
源码查看:
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
通过上面分析,内存泄漏的场景只有,当ThreadLocalRef为null,并且之后该线程放入线程池中,后面没有调用过get,set以及remove方法。那么ThreadLocalMap对象中的ThradLocal对象被回收,ThreadLocalMap的value--Page对象就存在堆内存中无法回收,造成内存泄漏。