ThreadLocal内存泄露

一、必备知识

1. 内存泄漏:

内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

2. Java中的引用分类:
引用类型引用类(接口Reference)代码体现GC情况
强引用Student student = new Student()只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,JVM也会直接抛出OutOfMemoryError,不会去回收。如果想中断强引用与对象之间的联系,可以显示的将强引用赋值为null,这样一来,JVM就可以适时的回收对象了
软引用SoftReferenceSoftReference softReference = new SoftReference(new Student())在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象
弱引用WeakReferenceWeakReference weakReference= new WeakReference(new Student())无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收
虚引用PhantomReferencePhantomReference phantomReference= new PhantomReference(new Student())如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收

二、ThreadLocal 与 Thread的ThreadLocalMap关系模型

在这里插入图片描述

1. ThreadLocal 的set方法
    public void set(T value) {
    	// 获取当前线程
        Thread t = Thread.currentThread();
        // 获取线程中的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        if (map != null)
        	// 调用ThreadLocalMap的set方法
            map.set(this, value);
        else
            createMap(t, value);
    }
2. ThreadLocalMap的set方法源码分析
static class ThreadLocalMap{

        static class Entry extends WeakReference<ThreadLocal<?>> {
            // ThreadLocal  set的值
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
            	// ThreadLocal自己
                super(k);
                value = v;
            }
        }
		// 保存 ThreadLocal  设置值的数组
        private Entry[] table;
        // 主要分析此方法
        private void set(ThreadLocal<?> key, Object value) {
			
            Entry[] tab = table;
            int len = tab.length;
            // ThreadLocal的hash值&数组长度减一能算出下标;
            int i = key.threadLocalHashCode & (len-1);
			// 当前下标得Entyr取出
            for (Entry e = tab[i];
                 e != null;  // 不等于 null,则执行代码;第一次都是null,直接跳过了
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
				// 如果当前ThreadLocal   和 存入的ThreadLocal相同,则直接替换其值 
                if (k == key) {
                    e.value = value;
                    return;
                }
				// 如果k== null,说明弱引用断了;使用当前的ThreadLocal去填充
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
			// 第一次  创建新得Entry,将ThreadLocal以及value都存入;
			// 此时要注意ThreadLocal是交给了WeakReference去维护了,而WeakReference是弱引用,GC的时候会把ThreadLocal GC掉;那就无法通过ThreadLocal的Hash计算下标访问entry了,这个value也就无法被访问了,只要线程不死,这个value就一致存在释放不掉;
            tab[i] = new Entry(key, value);
            // 条目总数 +1
            int sz = ++size;
            // 删除弱引用断了的元素
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
        private boolean cleanSomeSlots(int i, int n) {
            boolean removed = false;
            Entry[] tab = table;
            int len = tab.length;
            do {
                i = nextIndex(i, len);
                Entry e = tab[i];
                if (e != null && e.get() == null) {
                    n = len;
                    removed = true;
                    i = expungeStaleEntry(i);
                }
            } while ( (n >>>= 1) != 0);
            return removed;
        }
        
        private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                	// 如果存放的值的key和当前ThreadLocal一致,则清空
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }
    }
三、总结:

至此就分析完毕了,内存泄漏的原因就是由于threadlocal给弱引用维护了,如果threadlocal被gc掉,那就无法通过threadlocal再获得他所设置的变量了;
如果线程长时间存在比如线程池的核心线程,使用threadlocal去设置变量的话,就永远存在内存中了,无法被释放掉;
所以使用线程池应注意该问题;

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值