一、必备知识
1. 内存泄漏:
内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
2. Java中的引用分类:
引用类型 | 引用类(接口Reference) | 代码体现 | GC情况 |
---|---|---|---|
强引用 | 无 | Student student = new Student() | 只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,JVM也会直接抛出OutOfMemoryError,不会去回收。如果想中断强引用与对象之间的联系,可以显示的将强引用赋值为null,这样一来,JVM就可以适时的回收对象了 |
软引用 | SoftReference | SoftReference softReference = new SoftReference(new Student()) | 在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象 |
弱引用 | WeakReference | WeakReference weakReference= new WeakReference(new Student()) | 无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收 |
虚引用 | PhantomReference | PhantomReference phantomReference= new PhantomReference(new Student()) | 如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收 |
二、ThreadLocal 与 Thread的ThreadLocalMap关系模型
1. ThreadLocal 的set方法
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取线程中的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
if (map != null)
// 调用ThreadLocalMap的set方法
map.set(this, value);
else
createMap(t, value);
}
2. ThreadLocalMap的set方法源码分析
static class ThreadLocalMap{
static class Entry extends WeakReference<ThreadLocal<?>> {
// ThreadLocal set的值
Object value;
Entry(ThreadLocal<?> k, Object v) {
// ThreadLocal自己
super(k);
value = v;
}
}
// 保存 ThreadLocal 设置值的数组
private Entry[] table;
// 主要分析此方法
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// ThreadLocal的hash值&数组长度减一能算出下标;
int i = key.threadLocalHashCode & (len-1);
// 当前下标得Entyr取出
for (Entry e = tab[i];
e != null; // 不等于 null,则执行代码;第一次都是null,直接跳过了
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
// 如果当前ThreadLocal 和 存入的ThreadLocal相同,则直接替换其值
if (k == key) {
e.value = value;
return;
}
// 如果k== null,说明弱引用断了;使用当前的ThreadLocal去填充
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// 第一次 创建新得Entry,将ThreadLocal以及value都存入;
// 此时要注意ThreadLocal是交给了WeakReference去维护了,而WeakReference是弱引用,GC的时候会把ThreadLocal GC掉;那就无法通过ThreadLocal的Hash计算下标访问entry了,这个value也就无法被访问了,只要线程不死,这个value就一致存在释放不掉;
tab[i] = new Entry(key, value);
// 条目总数 +1
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;
}
private void remove(ThreadLocal<?> key) {
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)]) {
if (e.get() == key) {
// 如果存放的值的key和当前ThreadLocal一致,则清空
e.clear();
expungeStaleEntry(i);
return;
}
}
}
}
三、总结:
至此就分析完毕了,内存泄漏的原因就是由于threadlocal给弱引用维护了,如果threadlocal被gc掉,那就无法通过threadlocal再获得他所设置的变量了;
如果线程长时间存在比如线程池的核心线程,使用threadlocal去设置变量的话,就永远存在内存中了,无法被释放掉;
所以使用线程池应注意该问题;