问题:?
- Java的引用类型有哪几种?
- 每种引用类型的特点是什么?
- 每种引用类型的应用场景是什么?
- Threadlocal应用场景?
- ThreadLocal会产生内存泄漏?
一、ThreadLocal用来干什么?
目的为不同的线程创建一个变量副本;在多线程环境下可以访问对应的副本,也就是变量在不同的线程中是相互隔离的,是线程独享的
二、引用的分类
空间不够时,软引用指向的对象,在空间不够时会被回收,适合缓存;
弱引用遭遇到GC就会被回收
**虚引用的作用:**主要用来管理堆外内存。例如:当虚引用被回收时,会往队列里通知被回收的消息,这个时候jvm就可以回收,堆外内存了。清理堆外内存
三、源码分析
get、set、remove方法
public T get() {
Thread t = Thread.currentThread();
// 从当前线程中 获得ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
// 存在就添加 没有就设置初始化方法
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
3.1 Entry结构
每一个Thread线程都有一个属性名为 threadLocals 的ThreadLocalMap; 这个ThreadLocalMap 的管理者就是:定义的ThreadLocal对象;
ThreadLocalMap的结构; key = ThreadLocal对象; value也就是ThreadLocal定义的值;
// threadLocals 属性
ThreadLocal.ThreadLocalMap threadLocals = null;
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
四、ThreadLocal内存泄漏问题
4.1、关系图(ThreadLocal、Thread、ThreadLocalMap)
4.2 为什么ThreadLocal的entry要使用弱引用?
如果是强引用,那么就算 threadLocalRef对象设置为null,ThreadLocal对象也不会回收,因为此时被entry的key强引用了。这个时候如果Tread线程一直不结束,那么就可能发生** 内存泄漏。**
但是如果是弱引用,下次GC时threadLocal会可以被回收。
4.3 entry的弱引用就一定不会内存泄漏吗?
当threadLocal被回收后,entry的key就是null,此时就无法触及value,那么这一条引用链条就一直存在,导致value无法被回收,依旧可能发生内存泄漏。
建议:再使用完threadLocal时需要调用remove() 方法,方法内部会直接 移除 threadLocal所对应的entry对象;
4.4 如果忘记remove还有补救的措施吗?
CurrentThread依然运行的前提下,就算忘记调用remove方法,弱引用比强引用可以多一层保障:弱引用的ThreadLocal会被回收,对应的value在下一次ThreadLocalMap调用set,get,remove中的任一方法的时候会被清除,从而避免内存泄漏。(前提 弱引用)
源码如下:
// 次方法会在get方法中被调用
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
// 如果key为空 就会进行清理
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
小结:
虽然在执行get\set方法特定时机会清理key = null的 value,但是这也是不及时的,而且还是在这特定的调用才会发生。所以还是建议在使用完threadlocal变量后显式调用remove。(可以避免在线程池,线程复用的时候 数据污染)