提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
强软弱虚引用
强引用:gc时不会回收
软引用:内存不够用时,gc才会回收
弱引用:只要gc就会回收
虚引用:是否回收都找不到引用的对象
一、弱引用的例子
1.引入库
代码如下(示例):
public class WeakReferenceTest {
static ThreadLocalTest threadLocalTest = new ThreadLocalTest();
static WeakReference weakReference = new WeakReference(threadLocalTest);
public static void main(String[] args) throws InterruptedException {
SoftReference softReference = new SoftReference(new ThreadLocalTest());
System.out.println("GCh回收前,弱引用------------------"+weakReference.get());
System.out.println("GCh回收前,软引用------------------"+softReference.get());
threadLocalTest=null;
System.gc();
System.out.println("GCh回收后,弱引用------------------"+weakReference.get());
System.out.println("GCh回收后,软引用------------------"+softReference.get());
}
}
运行结果如下
GCh回收前,弱引用------------------ThreadLocalTest@119d7047
GCh回收前,软引用------------------ThreadLocalTest@776ec8df
GCh回收后,弱引用------------------null
GCh回收后,软引用------------------ThreadLocalTest@776ec8df
需要注意的程序里面有这么一句threadLocalTest=null; 如果没有了这句的话,weakReference是不会被GC回收的,为什么?
因为引用对象如果存在强引用的话,那是不能被GC回收的,要注意,弱引用:只要gc就会回收的前提是该对象只存在弱引用,或者是比弱引用更弱的引用(哈哈,是不是很绕。总而言之,只有对象不存在强引用时,弱引用才会被回收)。有了这个基础后,我们再看看threadlocal的问题。
二、ThreadLocal为什么会内存泄漏
虚线为弱引用, 实线为强引用。
ThreadLocalMap使用ThreadLocal的弱引用作为key。
1.如果一个ThreadLocal没有外部强引用来引用它(ThreadLocal ref 与ThreadLocal之间没有联系)那么系统 GC 的时候,这个ThreadLocal会被回收。
ThreadLocal回收之后,ThreadLocalMap中就会出现key为null的Entry,那么这些key为null的value,我们是无法获取的,但这些key为null的Entry的value会一直存在,因为另外一条强引用链一直存在:Current Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value无法回收,这会导致内存泄漏。
但实际上,ThreadLocalMap对这种情况也做了一些处理:在ThreadLocal的get(),set(),remove()的时候都会调用expungeStaleEntry()方法,帮我们清除key为null的值。
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程下的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
//传入ThreadLocal 作为key 来获取设置的值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
//如果Entry不为空,key不为空 (ThreadLocal)
if (e != null && e.get() == key)
return e;
else
//key 为空的情况下
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
//循环遍历Entry
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
//key为空的情况下,执行(实际上就是帮我们清空数据,让gc可以回收)
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//key为null 时,把对应的value 设置为null,给GC 回收
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
虽然ThreadLocal为我们做了这些处理,但是如果程序里面没有执行get(),set(),remove()的话,那内存泄漏的问题依旧存在,所以使用虚引用,只能帮我们某种程度上减少内存泄漏的问题。 为了避免内存泄漏,当我们每次使用完ThreadLocal,都必须调用它的remove()方法,清除数据。
总结
使用完ThreadLocal,都必须都调用它的remove()方法。
使用完ThreadLocal,都必须都调用它的remove()方法。
使用完ThreadLocal,都必须都调用它的remove()方法。
重要的事情说三遍!!!
为什么,防止内存泄漏!!!