ThreadLocal内存泄漏原因

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


强软弱虚引用

强引用: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()方法。
重要的事情说三遍!!!
为什么,防止内存泄漏!!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值