欢迎关注公众号【Ccww笔记】,原创技术文章第一时间推出java
往期文章:面试
在分析ThreadLocal致使的内存泄露前,须要普及了解一下内存泄露、强引用与弱引用以及GC回收机制,这样才能更好的分析为何ThreadLocal会致使内存泄露呢?更重要的是知道该如何避免这样状况发生,加强系统的健壮性。数组
内存泄露为程序在申请内存后,没法释放已申请的内存空间,一次内存泄露危害能够忽略,但内存泄露堆积后果很严重,不管多少内存,早晚会被占光,缓存
广义并通俗的说,就是:再也不会被使用的对象或者变量占用的内存不能被回收,就是内存泄露。多线程
强引用,使用最广泛的引用,一个对象具备强引用,不会被垃圾回收器回收。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不回收这种对象。并发
若是想取消强引用和某个对象之间的关联,能够显式地将引用赋值为null,这样可使JVM在合适的时间就会回收该对象。post
弱引用,JVM进行垃圾回收时,不管内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。能够在缓存中使用弱引用。this
JVM如何找到须要回收的对象,方式有两种:spa
引用计数法,可能会出现A 引用了 B,B 又引用了 A,这时候就算他们都再也不使用了,但由于相互引用 计数器=1 永远没法被回收。线程
先从前言的了解了一些概念(已懂忽略),接下来咱们开始正式的来理解ThreadLocal致使的内存泄露的解析。
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的实现原理,每个Thread维护一个ThreadLocalMap,key为使用弱引用的ThreadLocal实例,value为线程变量的副本。这些对象之间的引用关系以下,
实心箭头表示强引用,空心箭头表示弱引用
从上图中能够看出,hreadLocalMap使用ThreadLocal的弱引用做为key,若是一个ThreadLocal不存在外部强引用时,Key(ThreadLocal)势必会被GC回收,这样就会致使ThreadLocalMap中key为null, 而value还存在着强引用,只有thead线程退出之后,value的强引用链条才会断掉。
但若是当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永远没法回收,形成内存泄漏。
咱们看看Key使用的
当hreadLocalMap的key为强引用回收ThreadLocal时,由于ThreadLocalMap还持有ThreadLocal的强引用,若是没有手动删除,ThreadLocal不会被回收,致使Entry内存泄漏。
当ThreadLocalMap的key为弱引用回收ThreadLocal时,因为ThreadLocalMap持有ThreadLocal的弱引用,即便没有手动删除,ThreadLocal也会被回收。当key为null,在下一次ThreadLocalMap调用set(),get(),remove()方法的时候会被清除value值。
在这里只分析remove()方式,其余的方法能够查看源码进行分析:
private void remove(ThreadLocal<?> key) {
//使用hash方式,计算当前ThreadLocal变量所在table数组位置
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
//再次循环判断是否在为ThreadLocal变量所在table数组位置
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
//调用WeakReference的clear方法清除对ThreadLocal的弱引用
e.clear();
//清理key为null的元素
expungeStaleEntry(i);
return;
}
}
}
复制代码
再看看清理key为null的元素expungeStaleEntry(i):
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 根据强引用的取消强引用关联规则,将value显式地设置成null,去除引用
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// 从新hash,并对table中key为null进行处理
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//对table中key为null进行处理,将value设置为null,清除value的引用
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
复制代码
因为Thread中包含变量ThreadLocalMap,所以ThreadLocalMap与Thread的生命周期是同样长,若是都没有手动删除对应key,都会致使内存泄漏。
可是使用弱引用能够多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set(),get(),remove()的时候会被清除。
所以,ThreadLocal内存泄漏的根源是:因为ThreadLocalMap的生命周期跟Thread同样长,若是没有手动删除对应key就会致使内存泄漏,而不是由于弱引用。
每次使用完ThreadLocal都调用它的remove()方法清除数据
将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任什么时候候都能经过ThreadLocal的弱引用访问到Entry的value值,进而清除掉 。
各位看官还能够吗?喜欢的话,动动手指点个💗,点个关注呗!!谢谢支持!
欢迎关注公众号【Ccww笔记】,原创技术文章第一时间推出