网站部分乱码,网站建设前期分析的内容,手机服务器,网站建设公司是干嘛的前言 在分析ThreadLocal导致的内存泄露前#xff0c;需要普及了解一下内存泄露、强引用与弱引用以及GC回收机制#xff0c;这样才能更好的分析为什么ThreadLocal会导致内存泄露呢#xff1f;更重要的是知道该如何避免这样情况发生#xff0c;增强系统的健壮性。
内存泄露 …前言 在分析ThreadLocal导致的内存泄露前需要普及了解一下内存泄露、强引用与弱引用以及GC回收机制这样才能更好的分析为什么ThreadLocal会导致内存泄露呢更重要的是知道该如何避免这样情况发生增强系统的健壮性。
内存泄露 内存泄露为程序在申请内存后无法释放已申请的内存空间一次内存泄露危害可以忽略但内存泄露堆积后果很严重无论多少内存,迟早会被占光 广义并通俗的说就是不再会被使用的对象或者变量占用的内存不能被回收就是内存泄露。
强引用与弱引用
强引用 使用最普遍的引用一个对象具有强引用不会被垃圾回收器回收。当内存空间不足Java虚拟机宁愿抛出OutOfMemoryError错误使程序异常终止也不回收这种对象。 如果想取消强引用和某个对象之间的关联可以显式地将引用赋值为null这样可以使JVM在合适的时间就会回收该对象。 弱引用 JVM进行垃圾回收时无论内存是否充足都会回收被弱引用关联的对象。在java中用java.lang.ref.WeakReference类来表示。可以在缓存中使用弱引用。
GC回收机制-如何找到需要回收的对象
JVM如何找到需要回收的对象方式有两种
引用计数法每个对象有一个引用计数属性新增一个引用时计数加1引用释放时计数减1计数为0时可以回收可达性分析法从 GC Roots 开始向下搜索搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时则证明此对象是不可用的那么虚拟机就判断是可回收对象。 引用计数法可能会出现A 引用了 BB 又引用了 A这时候就算他们都不再使用了但因为相互引用 计数器1 永远无法被回收。 ThreadLocal的内存泄露分析 先从前言的了解了一些概念已懂忽略接下来我们开始正式的来理解ThreadLocal导致的内存泄露的解析。
实现原理
static class ThreadLocalMap {static class Entry extends WeakReferenceThreadLocal? {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal? k, Object v) {super(k);value v;}}...}
ThreadLocal实现原理 每一个Thread维护一个ThreadLocalMapkey为使用弱引用的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使用强引用和弱引用分别会有什么问题。
key 使用强引用 当hreadLocalMap的key为强引用那么回收ThreadLocal时因为ThreadLocalMap还持有ThreadLocal的强引用如果没有手动删除ThreadLocal就不会被回收导致Entry内存泄漏。
key 使用弱引用 当ThreadLocalMap的key为弱引用那么回收ThreadLocal时由于ThreadLocalMap持有ThreadLocal的弱引用即使没有手动删除ThreadLocal也会被回收。回收ThreadLocal后key为null在下一次ThreadLocalMap调用set()、get()、remove()方法的时候value值会被清除。
ThreadLocalMap的remove()分析
在这里只分析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就会容易导致内存泄漏而不是因为ThreadLocalMap的key是弱引用。
ThreadLocal正确的使用方法
每次使用完ThreadLocal都调用它的remove()方法清除数据将ThreadLocal变量定义成private static这样就一直存在ThreadLocal的强引用ThreadLocal对象不会被清除也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值进而清除掉 。