ThreadLocal - 内存泄漏

需求

看到有人说:Threadlocal的错误使用,会导致内存溢出,所以来分析一下源码。

结论

简而言之,就是通过 ThreadLocal 添加的 value 与 Thread 是强引用关系,在 Thread 无法正常销毁的场景下,有内存泄漏的隐患,例如:线程池。

解决方案:代码执行完成之后,要注意调用 remove() 函数清除数据。

反例

import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

class Test{
	

    /**
     * 代码功能:通过 ThreadLocal 设值,使用结束之后,未清除数据,后续所有的循环都能读到这个值。
     *
     * 代码分析:正常的业务中,运行结束之后,线程被回收,处于闲置状态,但是这个值依然保存在内存中,这显然不合适。
     *
     * @param args -
     */
    public static void main(String[] args) {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(1);
        executor.setMaxPoolSize(1);
        executor.setThreadNamePrefix("test-");
        executor.initialize();

        ThreadLocal<String> local = new ThreadLocal<>();

        int cnt = 100;
        while (cnt-- > 0) {
            int finalCnt = cnt;
            executor.execute(() -> {
                System.out.println(finalCnt);
                if (local.get() != null) {
                    System.out.println("已经存在值:" + local.get());
                } else {
                    local.set("cnt: " + finalCnt);
                }
            });
        }
    }
}

分析

只需要关注 ThreadLocal 的 set() 函数即可,

观察 set() 函数,就能发现这样一条引用链:Thread > ThreadLocalMap > Entry > value。

线程是特殊的对象,它是可回收的,还会出现永久锁死的情况,因此需要关注内存泄漏的问题。

其它注意点:

  1. Entry 对象虽然继承自 WeakReference(弱引用),但是只与 key 值是弱引用关系,与 value 值是强引用关系;
  2. 引用链不包含 ThreadLocal 本身,ThreadLocal 只是一个用于存取值的工具;
  3. 因为引用链不包含 ThreadLocal,因此将 ThreadLocal 声明成 static 变量,也是可以的;
/**
 * Thread 关键源码
 */
public class Thread implements Runnable {

	// 每一个线程内部都有一个 Map,很明显存在隐患:如果未清理,数据会越堆越多。
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

/**
 * ThreadLocal 关键源码
 */
public class ThreadLocal<T> {

	// 设值函数,过程不涉及成员变量,因此 ThreadLocal 本身没有泄漏的风险
    public void set(T value) {
		// Thread 与 ThreadLocalMap 之间是强引用关系
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }

	// ThreadLocalMap 关键代码
    static class ThreadLocalMap {
		
		// 拥有 Entry 的引用
		private Entry[] table;

        static class Entry extends WeakReference<ThreadLocal<?>> {
            // 注意:这里是强引用关系
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
	}
}

posted on 2019-08-22 00:36  疯狂的妞妞  阅读(168)  评论(0编辑  收藏  举报

导航